Инвесторы из фонда «Shut Up and Take My Money» решили открыть заведение общественного питания в Москве. Нужно решить:
Основателям фонда «Shut Up and Take My Money» хотят повторить успех сериала «Друзья» — открыть такую же крутую и доступную, как «Central Perk», кофейню в Москве. Заказчики не боятся конкуренции в этой сфере. Нужно исследовать рынок кофеен в Москве и выдать рекомендации по открытию**
Файл moscow_places.csv:
1. Загрузка данных и изучение общей информации
2. Предобработка данных
2.1. Проверка на дубликаты
2.2. Создание дополнительных столбцов
3. Анализ данных
3.1. Анализ категорий заведений
3.2. Анализ соотношения сетевых и не сетевых заведений в датасете
3.3. Топ 15 популярных сетей в Москве
3.4. Анализ административных округов Москвы присутствующих в датасете
3.5 Анализ и визуализация распределения средних рейтингов по категориям заведений
3.6. Определение и анализ топ-15 улиц по количеству заведений
3.7. Определение улиц на которых находится только один объект общепита и анализ этих заведений
3.8. Анализ и сравнение цен в Центральном административном округе и других
3.9. Длполнительный анализ.
3.10. Общий вывод.
4. Детализированное исследование: открытие кофейни.
4.1 Вывод и рекомендации.
5. Презентация.
# импортируем все необходимые библиотеки
import pandas as pd
import datetime as dt
import numpy as np
import matplotlib.pyplot as plt
from pandas.plotting import register_matplotlib_converters
import seaborn as sns
import warnings
warnings.filterwarnings('ignore')
import plotly
import plotly.graph_objs as go
import plotly.express as px
from plotly.subplots import make_subplots
import math as mth
from scipy import stats as st
from folium import Map, Marker
from folium.plugins import MarkerCluster
from folium import Map, Choropleth
pd.set_option('display.max_colwidth', 0)
# Загрузим данные о заведениях общественного питания Москвы
path = '/datasets/'
data = pd.read_csv(path + 'moscow_places.csv')
# выведем верх таблицы на экран
display(data.head())
| name | category | address | district | hours | lat | lng | rating | price | avg_bill | middle_avg_bill | middle_coffee_cup | chain | seats | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | WoWфли | кафе | Москва, улица Дыбенко, 7/1 | Северный административный округ | ежедневно, 10:00–22:00 | 55.878494 | 37.478860 | 5.0 | NaN | NaN | NaN | NaN | 0 | NaN |
| 1 | Четыре комнаты | ресторан | Москва, улица Дыбенко, 36, корп. 1 | Северный административный округ | ежедневно, 10:00–22:00 | 55.875801 | 37.484479 | 4.5 | выше среднего | Средний счёт:1500–1600 ₽ | 1550.0 | NaN | 0 | 4.0 |
| 2 | Хазри | кафе | Москва, Клязьминская улица, 15 | Северный административный округ | пн-чт 11:00–02:00; пт,сб 11:00–05:00; вс 11:00–02:00 | 55.889146 | 37.525901 | 4.6 | средние | Средний счёт:от 1000 ₽ | 1000.0 | NaN | 0 | 45.0 |
| 3 | Dormouse Coffee Shop | кофейня | Москва, улица Маршала Федоренко, 12 | Северный административный округ | ежедневно, 09:00–22:00 | 55.881608 | 37.488860 | 5.0 | NaN | Цена чашки капучино:155–185 ₽ | NaN | 170.0 | 0 | NaN |
| 4 | Иль Марко | пиццерия | Москва, Правобережная улица, 1Б | Северный административный округ | ежедневно, 10:00–22:00 | 55.881166 | 37.449357 | 5.0 | средние | Средний счёт:400–600 ₽ | 500.0 | NaN | 1 | 148.0 |
# посчитаем общее количество заведений Москвы
total = data.shape[0]
print('Общее количество заведений общественного питания в Москве: ', total)
Общее количество заведений общественного питания в Москве: 8406
# посмотрим информацию о таблице
data.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 8406 entries, 0 to 8405 Data columns (total 14 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 name 8406 non-null object 1 category 8406 non-null object 2 address 8406 non-null object 3 district 8406 non-null object 4 hours 7870 non-null object 5 lat 8406 non-null float64 6 lng 8406 non-null float64 7 rating 8406 non-null float64 8 price 3315 non-null object 9 avg_bill 3816 non-null object 10 middle_avg_bill 3149 non-null float64 11 middle_coffee_cup 535 non-null float64 12 chain 8406 non-null int64 13 seats 4795 non-null float64 dtypes: float64(6), int64(1), object(7) memory usage: 919.5+ KB
В датасете представлено 8406 заведений обществвенного питания Москвы.
В остальных столбцах:
Датасет хранит 7 строковых столбцов, 6 численных и 1 целочисленный.
Заменим все прописные буквы на строковые в названиях - столбец 'name' и адресах - 'address'
для удобства поиска дубликатов.
# сделаем названия заведений и адресов строчными чтобы избежать дубликатов
a = data['address'].str.lower()
data['address'] = a
b = data['name'].str.lower()
data['name'] = b
# проверим результат выведя верх таблицы
data.head()
| name | category | address | district | hours | lat | lng | rating | price | avg_bill | middle_avg_bill | middle_coffee_cup | chain | seats | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | wowфли | кафе | москва, улица дыбенко, 7/1 | Северный административный округ | ежедневно, 10:00–22:00 | 55.878494 | 37.478860 | 5.0 | NaN | NaN | NaN | NaN | 0 | NaN |
| 1 | четыре комнаты | ресторан | москва, улица дыбенко, 36, корп. 1 | Северный административный округ | ежедневно, 10:00–22:00 | 55.875801 | 37.484479 | 4.5 | выше среднего | Средний счёт:1500–1600 ₽ | 1550.0 | NaN | 0 | 4.0 |
| 2 | хазри | кафе | москва, клязьминская улица, 15 | Северный административный округ | пн-чт 11:00–02:00; пт,сб 11:00–05:00; вс 11:00–02:00 | 55.889146 | 37.525901 | 4.6 | средние | Средний счёт:от 1000 ₽ | 1000.0 | NaN | 0 | 45.0 |
| 3 | dormouse coffee shop | кофейня | москва, улица маршала федоренко, 12 | Северный административный округ | ежедневно, 09:00–22:00 | 55.881608 | 37.488860 | 5.0 | NaN | Цена чашки капучино:155–185 ₽ | NaN | 170.0 | 0 | NaN |
| 4 | иль марко | пиццерия | москва, правобережная улица, 1б | Северный административный округ | ежедневно, 10:00–22:00 | 55.881166 | 37.449357 | 5.0 | средние | Средний счёт:400–600 ₽ | 500.0 | NaN | 1 | 148.0 |
# проверим явные дубликаты
data.duplicated().sum()
0
Явные дубликаты отсутствуют
# посмотрим пропуски в столбцах
#выводим количество пропусков в каждом из столбцов
display(data.isna().sum())
name 0 category 0 address 0 district 0 hours 536 lat 0 lng 0 rating 0 price 5091 avg_bill 4590 middle_avg_bill 5257 middle_coffee_cup 7871 chain 0 seats 3611 dtype: int64
# то же самое в процентах с градиентом
display('Количество пропусков в %%')
pd.DataFrame(round(data.isna().mean()*100,1)).style.background_gradient('coolwarm')
'Количество пропусков в %%'
| 0 | |
|---|---|
| name | 0.000000 |
| category | 0.000000 |
| address | 0.000000 |
| district | 0.000000 |
| hours | 6.400000 |
| lat | 0.000000 |
| lng | 0.000000 |
| rating | 0.000000 |
| price | 60.600000 |
| avg_bill | 54.600000 |
| middle_avg_bill | 62.500000 |
| middle_coffee_cup | 93.600000 |
| chain | 0.000000 |
| seats | 43.000000 |
Мы видим, что максимальное количество пропусков в столбце 'middle_coffee_cup' - оно и понятно, если это не кофейня, то кто будет заморачиваться со средней ценой чашечки каппучино? - 93,6% пропусков в столбце. Только 6,4% данных заполнено в этом столбце.
Еще один столбец с пропусками 62,5% - это 'middle_avg_bill' (срединный средний чек) - это числовое значение из диапазона или из одного числа столбца 'avg_bill'(средний чек) - теоретически из этого столбца можно было бы взять данные 7,9%. Но, поскольку данные собраны из разных источников и заполнение не будет корректным, кроме того удаление такого количества данных исказит результаты исследования, поэтому оставим пропуски так как есть. В дальнейшем мы построим несколько визуализаций, доказывающих правильность нашего выбора.
Еще один недозаполненный столбец 'price' - это градация заведений по среднему чеку - 60% пропуски, поступим аналогично столбцу 'middle_avg_bill'.
Кроме того есть пропуски в столбце 'hours' - часы работы 6,4% это не существенно.
По поводу заполнения пропусков предлагаю построить визуализации и определить - можно ли дозаполнить значения.
# создадим столбец с улицей разделив строку в столбце address, разделитель - запятая и возьмем вторую позицию в кортеже
data['street'] = data['address'].str.split(', ').str[1]
# проверим появление столбца
display(data.info())
# проверим правильность отчечения улицы из адреса
data[['address', 'street']].head()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 8406 entries, 0 to 8405 Data columns (total 15 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 name 8406 non-null object 1 category 8406 non-null object 2 address 8406 non-null object 3 district 8406 non-null object 4 hours 7870 non-null object 5 lat 8406 non-null float64 6 lng 8406 non-null float64 7 rating 8406 non-null float64 8 price 3315 non-null object 9 avg_bill 3816 non-null object 10 middle_avg_bill 3149 non-null float64 11 middle_coffee_cup 535 non-null float64 12 chain 8406 non-null int64 13 seats 4795 non-null float64 14 street 8406 non-null object dtypes: float64(6), int64(1), object(8) memory usage: 985.2+ KB
None
| address | street | |
|---|---|---|
| 0 | москва, улица дыбенко, 7/1 | улица дыбенко |
| 1 | москва, улица дыбенко, 36, корп. 1 | улица дыбенко |
| 2 | москва, клязьминская улица, 15 | клязьминская улица |
| 3 | москва, улица маршала федоренко, 12 | улица маршала федоренко |
| 4 | москва, правобережная улица, 1б | правобережная улица |
# выясним сколько заведений работют 24/7
# заменим пропуски на значение 'unknown', чтобы можно было обрабатывать значения
data['hours'] = data['hours'].fillna('unknown')
# присвоим значения False всему столбцу 24/7
data['is_24/7'] = False
# перезапишем значение столбца на True
data.loc[data['hours']=='ежедневно, круглосуточно','is_24/7']=True
print('количество заведений работающих 24/7: ', len(data[data['is_24/7'] == True]))
print('количество вариантов часов работы всего:', len(data['hours'].value_counts()))
# выведем на экран верх столбца с часами работы заедений
display(data['hours'].value_counts().head(10))
# выведем на экран количество зачедений работающих 24/7 - да/нет
print('количество заведений работающих 24/7 - да/нет')
display(data['is_24/7'].value_counts())
количество заведений работающих 24/7: 730 количество вариантов часов работы всего: 1308
ежедневно, 10:00–22:00 759 ежедневно, круглосуточно 730 unknown 536 ежедневно, 11:00–23:00 396 ежедневно, 10:00–23:00 310 ежедневно, 12:00–00:00 254 ежедневно, 09:00–21:00 204 ежедневно, 09:00–22:00 184 ежедневно, 12:00–23:00 178 ежедневно, 08:00–23:00 160 Name: hours, dtype: int64
количество заведений работающих 24/7 - да/нет
False 7676 True 730 Name: is_24/7, dtype: int64
Итого, мы видим, что из 1308 вариантов часов работы заведений 730 - работают круглосуточно и ежедневно (24/7), это 8,7% заведений. Самые популярные часы работы заведений общественного питания с 10.00 до 22.00 и ежедневно, круглосуточно. Кроме того обнаружилось 536 заведений не заполнили свои часы работы. Мы сохранили эти значения, потому что они составляют 6,37%, а это больше допустимых 5%.
# создадим копию исходной таблицы для дальнейшей работы
data1=data.copy()
#выводим на экран список уникальных названий округов
display(data['district'].unique())
#заменяем неявные дубликаты через словарь
data['district'].replace({'Северный административный округ':'САО',
'Северо-Восточный административный округ':'СВАО',
'Северо-Западный административный округ':'СЗАО',
'Центральный административный округ':'ЦАО',
'Восточный административный округ':'ВАО',
'Западный административный округ':'ЗАО',
'Юго-Восточный административный округ':'ЮВАО',
'Юго-Западный административный округ':'ЮЗАО',
'Южный административный округ':'ЮАО'}, inplace=True)
#выводим на экран список уникальных названий округов после переименования
display(data['district'].unique())
array(['Северный административный округ',
'Северо-Восточный административный округ',
'Северо-Западный административный округ',
'Западный административный округ',
'Центральный административный округ',
'Восточный административный округ',
'Юго-Восточный административный округ',
'Южный административный округ',
'Юго-Западный административный округ'], dtype=object)
array(['САО', 'СВАО', 'СЗАО', 'ЗАО', 'ЦАО', 'ВАО', 'ЮВАО', 'ЮАО', 'ЮЗАО'],
dtype=object)
# выведем на экран количество значений уникальных имен заведений
display(data['name'].value_counts().head(20))
кафе 189 шоколадница 120 домино'с пицца 77 додо пицца 74 one price coffee 72 яндекс лавка 69 cofix 65 prime 50 хинкальная 44 шаурма 43 кофепорт 42 кулинарная лавка братьев караваевых 39 теремок 38 чайхана 37 ресторан 34 буханка 32 cofefest 32 столовая 28 му-му 27 drive café 24 Name: name, dtype: int64
Мы видим, что, в списке топ названий заведений есть: "кафе", "хинкальная", "шаурма", "столовая", "чайхана" и "ресторан"(вынесем эти названия в стоплист). Скорее всего это не сети, а просто заведений без уникального названия.
# создадим список с названиями заведений, совпадающими с их категорией
no_name_list = ['кафе', 'ресторан', 'хинкальная', 'шаурма', 'чайхана', 'столовая']
# отсечем заведения из этого списка
data_without_noname = data[data['name'].isin(no_name_list) == False]
print('количество заведений с уникальными названиями всего', data_without_noname.shape[0])
количество заведений с уникальными названиями всего 8031
# выведем на экран верх таблицы data_without_noname
display(data_without_noname['name'].value_counts().head(20))
# проверим что получилось, выведем на экран информацию о таблице data_without_noname
display(data_without_noname.info())
шоколадница 120 домино'с пицца 77 додо пицца 74 one price coffee 72 яндекс лавка 69 cofix 65 prime 50 кофепорт 42 кулинарная лавка братьев караваевых 39 теремок 38 cofefest 32 буханка 32 му-му 27 drive café 24 кофемания 23 андерсон 22 крошка картошка 22 cinnabon 20 скалка 20 french bakery 20 Name: name, dtype: int64
<class 'pandas.core.frame.DataFrame'> Int64Index: 8031 entries, 0 to 8405 Data columns (total 16 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 name 8031 non-null object 1 category 8031 non-null object 2 address 8031 non-null object 3 district 8031 non-null object 4 hours 8031 non-null object 5 lat 8031 non-null float64 6 lng 8031 non-null float64 7 rating 8031 non-null float64 8 price 3268 non-null object 9 avg_bill 3765 non-null object 10 middle_avg_bill 3098 non-null float64 11 middle_coffee_cup 535 non-null float64 12 chain 8031 non-null int64 13 seats 4601 non-null float64 14 street 8031 non-null object 15 is_24/7 8031 non-null bool dtypes: bool(1), float64(6), int64(1), object(8) memory usage: 1011.7+ KB
None
# посмотрим распределение заведений сетевое/не сетевое
data['chain'].value_counts()
0 5201 1 3205 Name: chain, dtype: int64
5201 заведений в датасете не принадлежит сетям, 3205 - сетевые заведения
# посмотрим в датасете без уникальных названий распределений сетевых/не сетевых заведений
data_with_noname = data[data['name'].isin(no_name_list) == True]
data_with_noname['chain'].value_counts()
0 294 1 81 Name: chain, dtype: int64
Среди названий из стоплиста 81 заведений все-таки являются сетевыми
#заведений по признаку принадлежности сети
# выведем на экран количество сетей
display('количество сетевых заведений с уникальными названиями: ', data_without_noname[data_without_noname['chain'] ==1]
['name'].count())
# выведем на экран количество заведение=категория
display('количество сетевых заведений с не уникальными названиями (название сети=название категории) с названиями из стоплиста: ', \
data_with_noname[data_with_noname['chain']==1]['name'].count())
'количество сетевых заведений с уникальными названиями: '
3124
'количество сетевых заведений с не уникальными названиями (название сети=название категории) с названиями из стоплиста: '
81
#заведений по признаку принадлежности сети
# выведем на экран количество сетей
display('количество сетей с уникальными названиями: ', data_without_noname[data_without_noname['chain'] ==1]
['name'].nunique())
# выведем на экран количество сетей с не уникальными названиями
display('количество сетей с не уникальными названиями (название сети=название категории) с названиями из стоплиста: ', \
data_with_noname[data_with_noname['chain']==1]['name'].nunique())
'количество сетей с уникальными названиями: '
745
'количество сетей с не уникальными названиями (название сети=название категории) с названиями из стоплиста: '
2
# посмотрим какие неуникальные названия из стоплиста оказались сетями
data_with_noname_chain = data_with_noname[data_with_noname['chain'] == 1]
# выведем название и количество заведений в сетях с неуникальными названиями.
data_with_noname_chain['name'].value_counts()
хинкальная 44 чайхана 37 Name: name, dtype: int64
Cетевых заведений 3205, из них 3124 сети с уникальными названиями, следовательно часть сетевых заведений не имеют уникального названия и таких заведений 81. Сюда попали чайхоны (37) и хинкальные (44) у которых это название и есть сетевое имя.
Посмотрим распределение цен, ценовых категорий и средних чеков в самой распространенной сети заведений - "Шоколаднице". Если на лицо будет однородность распределения, то логично будет заменить пропуски средними значениями
# выведем таблицу, в которой будут полько "шоколадницы"
display(data_without_noname[data_without_noname['name']=='шоколадница'].head(20))
| name | category | address | district | hours | lat | lng | rating | price | avg_bill | middle_avg_bill | middle_coffee_cup | chain | seats | street | is_24/7 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 151 | шоколадница | кофейня | москва, дмитровское шоссе, 163ак1 | СВАО | ежедневно, 10:00–22:00 | 55.910447 | 37.542227 | 4.1 | NaN | Цена чашки капучино:239–274 ₽ | NaN | 256.0 | 1 | NaN | дмитровское шоссе | False |
| 240 | шоколадница | кофейня | москва, широкая улица, 13а | СВАО | ежедневно, 10:00–21:00 | 55.887990 | 37.662485 | 4.1 | NaN | Цена чашки капучино:239–274 ₽ | NaN | 256.0 | 1 | 75.0 | широкая улица | False |
| 343 | шоколадница | кофейня | москва, фестивальная улица, 13, корп. 1 | САО | ежедневно, 08:00–23:00 | 55.855703 | 37.477670 | 4.3 | NaN | Цена чашки капучино:239–274 ₽ | NaN | 256.0 | 1 | 100.0 | фестивальная улица | False |
| 360 | шоколадница | кофейня | москва, сходненская улица, 25 | СЗАО | ежедневно, 08:00–23:00 | 55.846066 | 37.438897 | 4.3 | средние | Цена чашки капучино:239–274 ₽ | NaN | 256.0 | 1 | 130.0 | сходненская улица | False |
| 381 | шоколадница | кофейня | москва, сходненская улица, 56 | СЗАО | ежедневно, 10:00–22:00 | 55.850700 | 37.443523 | 4.3 | NaN | NaN | NaN | NaN | 1 | 140.0 | сходненская улица | False |
| 455 | шоколадница | кофейня | москва, улица адмирала макарова, 6, стр. 13 | САО | пн-чт 08:00–22:00; пт 08:00–23:00; сб,вс 09:00–23:00 | 55.835660 | 37.490497 | 4.2 | средние | Цена чашки капучино:239–274 ₽ | NaN | 256.0 | 1 | 80.0 | улица адмирала макарова | False |
| 483 | шоколадница | кофейня | москва, кронштадтский бульвар, 3а | САО | ежедневно, 10:00–22:00 | 55.840448 | 37.485482 | 4.1 | NaN | Средний счёт:до 650 ₽ | 650.0 | NaN | 1 | NaN | кронштадтский бульвар | False |
| 512 | шоколадница | кафе | москва, ленинградское шоссе, 16а, стр. 4 | САО | ежедневно, 10:00–23:00 | 55.823176 | 37.497703 | 4.1 | NaN | NaN | NaN | NaN | 1 | 230.0 | ленинградское шоссе | False |
| 529 | шоколадница | кофейня | москва, головинское шоссе, 5, корп. 1 | САО | ежедневно, 10:00–22:00 | 55.840620 | 37.491368 | 4.1 | NaN | Цена чашки капучино:179–249 ₽ | NaN | 214.0 | 1 | 140.0 | головинское шоссе | False |
| 832 | шоколадница | кофейня | москва, дмитровское шоссе, 89 | САО | ежедневно, 10:00–22:00 | 55.863821 | 37.545451 | 4.0 | NaN | NaN | NaN | NaN | 1 | 55.0 | дмитровское шоссе | False |
| 941 | шоколадница | кофейня | москва, енисейская улица, 11 | СВАО | ежедневно, 08:00–23:00 | 55.865204 | 37.661509 | 4.3 | NaN | Цена чашки капучино:285–289 ₽ | NaN | 287.0 | 1 | 40.0 | енисейская улица | False |
| 977 | шоколадница | кофейня | москва, снежная улица, 27 | СВАО | пн-пт 09:00–22:00; сб,вс 10:00–22:00 | 55.856570 | 37.653402 | 4.3 | NaN | Цена чашки капучино:239–274 ₽ | NaN | 256.0 | 1 | 110.0 | снежная улица | False |
| 1027 | шоколадница | кофейня | москва, проезд дежнёва, 21 | СВАО | ежедневно, 10:00–22:00 | 55.870686 | 37.637864 | 4.3 | средние | Цена чашки капучино:239–274 ₽ | NaN | 256.0 | 1 | 62.0 | проезд дежнёва | False |
| 1119 | шоколадница | кофейня | москва, проспект мира, 119, стр. 23 | СВАО | ежедневно, 10:00–22:00 | 55.832547 | 37.618962 | 3.9 | выше среднего | Средний счёт:1000–1500 ₽ | 1250.0 | NaN | 1 | NaN | проспект мира | False |
| 1165 | шоколадница | кофейня | москва, лазоревый проезд, 1а, корп. 3 | СВАО | ежедневно, 08:00–23:00 | 55.847197 | 37.637798 | 4.9 | NaN | NaN | NaN | NaN | 1 | NaN | лазоревый проезд | False |
| 1190 | шоколадница | кофейня | москва, строгинский бульвар, 1, корп. 2 | СЗАО | ежедневно, 08:00–23:00 | 55.805747 | 37.396303 | 4.2 | NaN | Цена чашки капучино:150–290 ₽ | NaN | 220.0 | 1 | 70.0 | строгинский бульвар | False |
| 1282 | шоколадница | кофейня | москва, улица маршала бирюзова, 32 | СЗАО | ежедневно, 09:00–22:00 | 55.799797 | 37.483610 | 4.0 | средние | Цена чашки капучино:239–274 ₽ | NaN | 256.0 | 1 | 174.0 | улица маршала бирюзова | False |
| 1291 | шоколадница | кофейня | москва, улица народного ополчения, 49, корп. 1 | СЗАО | ежедневно, круглосуточно | 55.794815 | 37.494834 | 4.2 | средние | Средний счёт:650–850 ₽ | 750.0 | NaN | 1 | 200.0 | улица народного ополчения | True |
| 1363 | шоколадница | кофейня | москва, щукинская улица, 42 | СЗАО | ежедневно, 10:00–22:00 | 55.809257 | 37.464076 | 4.3 | средние | Средний счёт:700–1000 ₽ | 850.0 | NaN | 1 | 150.0 | щукинская улица | False |
| 1469 | шоколадница | кофейня | москва, хорошёвское шоссе, 27 | САО | ежедневно, 10:00–22:00 | 55.777623 | 37.523210 | 4.3 | NaN | Цена чашки капучино:239–274 ₽ | NaN | 256.0 | 1 | 45.0 | хорошёвское шоссе | False |
# выведем на экран описание таблицы с шоколадницами
display(data_without_noname[data_without_noname['name']=='шоколадница'].describe())
| lat | lng | rating | middle_avg_bill | middle_coffee_cup | chain | seats | |
|---|---|---|---|---|---|---|---|
| count | 120.000000 | 120.000000 | 120.000000 | 12.000000 | 62.000000 | 120.0 | 83.000000 |
| mean | 55.754365 | 37.593462 | 4.177500 | 679.166667 | 277.193548 | 1.0 | 135.144578 |
| std | 0.063562 | 0.094001 | 0.116289 | 289.559488 | 167.721944 | 0.0 | 140.968352 |
| min | 55.583134 | 37.382889 | 3.900000 | 300.000000 | 170.000000 | 1.0 | 25.000000 |
| 25% | 55.727909 | 37.524333 | 4.100000 | 400.000000 | 256.000000 | 1.0 | 61.500000 |
| 50% | 55.753751 | 37.597553 | 4.200000 | 650.000000 | 256.000000 | 1.0 | 96.000000 |
| 75% | 55.794872 | 37.645203 | 4.200000 | 775.000000 | 256.000000 | 1.0 | 147.000000 |
| max | 55.910447 | 37.819307 | 4.900000 | 1250.000000 | 1568.000000 | 1.0 | 1040.000000 |
# выведем на экран распределение категорий цен в таблице с шоколадницами
display(data_without_noname[data_without_noname['name']=='шоколадница']['price'].value_counts())
средние 49 выше среднего 2 низкие 1 Name: price, dtype: int64
Мы видим, что даже в рамках одной сети мы не можем заполнить значения столбца "middle_avg_bill" по данным из столбцов "middle_coffee_cup" и "avg_bill" потому что они разные.
Также, выведя на экран количество категорий цен в рамках сети Шоколадница - мы видим, что 4% - это цены выше среднего и 2 - низкие. В сумме это 6% цен отличаются от основной категории цен "средние", это более 3%, которые мы могли бы принять за погрешность.
Также в описании мы видим, что нет совпадения между средним и медианным значением рейтинга, среднего чека и среднего чека чашки кофе.
Если нет уникального среднего чека даже внутри сети, то разумно оставить столбец "middle_avg_bill" "как есть" и не обращать внимание на пропуски.
Очень интересная деталь шокладница на проспекте мира 119 стр. 23 имеет цены выше среднего, средний чек 1250 и рейтинг 3,9 - это самый низкий рейтинг из выведенных на экран.
# выведем на экран распределение заведений по категориям цен
data_price = data['price'].value_counts()
data_price = data_price.reset_index()
data_price
| index | price | |
|---|---|---|
| 0 | средние | 2117 |
| 1 | выше среднего | 564 |
| 2 | высокие | 478 |
| 3 | низкие | 156 |
# всего заведений, в которых указана категория цен
price_sum = data_price['price'].sum()
print('всего заведений, в которых указана категория цен: ', price_sum)
всего заведений, в которых указана категория цен: 3315
# добавим столбец с долей заведений от общего числа, указавших категорию цен
data_price['share'] = round(100*data_price['price']/price_sum,0)
display('уникальные названия столбца "price": ', data['price'].unique())
display('количество значений столбца "price": ', data_price)
'уникальные названия столбца "price": '
array([nan, 'выше среднего', 'средние', 'высокие', 'низкие'], dtype=object)
'количество значений столбца "price": '
| index | price | share | |
|---|---|---|---|
| 0 | средние | 2117 | 64.0 |
| 1 | выше среднего | 564 | 17.0 |
| 2 | высокие | 478 | 14.0 |
| 3 | низкие | 156 | 5.0 |
# построим столбчатую диаграмму для наглядности
fig = px.bar(data_price,
x=['средние', 'выше среднего', 'высокие', 'низкие'],
y='share',
title='Доля категорй цен price среди заведений Москвы')
fig.update_xaxes(tickangle=45)
fig.show()
# построим круговую диаграмму для наглядности
go.Figure(data=[go.Pie(labels=['средние', 'выше среднего', 'высокие', 'низкие'], \
values= data_price['price'])], \
layout = go.Layout(title=go.layout.Title(text='Круговая диаграмма доли уровня цен в заведениях Москвы')))
Распределение по уровню цен в Москве 2/3 заведений имеют средние цены. 1/6 часть заведений имеют цены выше среднего, 1/7 часть имеют цены - высокие. То есть, почти треть заведений имеют уровень цен высокие и выше среднего и лишь 5% - каждое двадцатое заведение имеет низкие цены.
Отсюда вывод: В Москве нет места низким ценам!
Или второй вариант - высокая стоимость аренды диктует либо цены высокие и выше среднего, либо средние цены и большое количество посетителей
# удалим из данных все строки в которых отсутствуют данные о среднем чеке и принадлежность к категории цен столбец 'price'
data_drop_na_price = data.dropna(subset=['price', 'middle_avg_bill'])
# перенумеруем этот датасет
data_drop_na_price = data_drop_na_price.reset_index(drop=True)
# посмотрим информацию, верх таблицы и описание
display(data_drop_na_price.info())
display(data_drop_na_price.head(20))
display(data_drop_na_price.describe())
<class 'pandas.core.frame.DataFrame'> RangeIndex: 2679 entries, 0 to 2678 Data columns (total 16 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 name 2679 non-null object 1 category 2679 non-null object 2 address 2679 non-null object 3 district 2679 non-null object 4 hours 2679 non-null object 5 lat 2679 non-null float64 6 lng 2679 non-null float64 7 rating 2679 non-null float64 8 price 2679 non-null object 9 avg_bill 2679 non-null object 10 middle_avg_bill 2679 non-null float64 11 middle_coffee_cup 0 non-null float64 12 chain 2679 non-null int64 13 seats 1681 non-null float64 14 street 2679 non-null object 15 is_24/7 2679 non-null bool dtypes: bool(1), float64(6), int64(1), object(8) memory usage: 316.7+ KB
None
| name | category | address | district | hours | lat | lng | rating | price | avg_bill | middle_avg_bill | middle_coffee_cup | chain | seats | street | is_24/7 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | четыре комнаты | ресторан | москва, улица дыбенко, 36, корп. 1 | САО | ежедневно, 10:00–22:00 | 55.875801 | 37.484479 | 4.5 | выше среднего | Средний счёт:1500–1600 ₽ | 1550.0 | NaN | 0 | 4.0 | улица дыбенко | False |
| 1 | хазри | кафе | москва, клязьминская улица, 15 | САО | пн-чт 11:00–02:00; пт,сб 11:00–05:00; вс 11:00–02:00 | 55.889146 | 37.525901 | 4.6 | средние | Средний счёт:от 1000 ₽ | 1000.0 | NaN | 0 | 45.0 | клязьминская улица | False |
| 2 | иль марко | пиццерия | москва, правобережная улица, 1б | САО | ежедневно, 10:00–22:00 | 55.881166 | 37.449357 | 5.0 | средние | Средний счёт:400–600 ₽ | 500.0 | NaN | 1 | 148.0 | правобережная улица | False |
| 3 | огни города | бар,паб | москва, клязьминская улица, 9, стр. 3 | САО | пн 15:00–04:00; вт-вс 15:00–05:00 | 55.890752 | 37.524653 | 4.4 | средние | Средний счёт:199 ₽ | 199.0 | NaN | 0 | 45.0 | клязьминская улица | False |
| 4 | mr. уголёк | быстрое питание | москва, клязьминская улица, 9, стр. 3 | САО | пн-чт 10:00–22:00; пт,сб 10:00–23:00; вс 10:00–22:00 | 55.890636 | 37.524303 | 4.7 | средние | Средний счёт:200–300 ₽ | 250.0 | NaN | 0 | 45.0 | клязьминская улица | False |
| 5 | donna maria | ресторан | москва, дмитровское шоссе, 107, корп. 4 | САО | ежедневно, 10:00–22:00 | 55.880045 | 37.539006 | 4.8 | средние | Средний счёт:от 500 ₽ | 500.0 | NaN | 0 | 79.0 | дмитровское шоссе | False |
| 6 | готика | кафе | москва, ангарская улица, 39 | САО | ежедневно, 12:00–00:00 | 55.879038 | 37.524487 | 4.3 | средние | Средний счёт:1000–1200 ₽ | 1100.0 | NaN | 0 | 65.0 | ангарская улица | False |
| 7 | заправка | кафе | москва, мкад, 80-й километр, 1 | САО | вт-сб 09:00–18:00 | 55.899938 | 37.517958 | 4.3 | средние | Средний счёт:330 ₽ | 330.0 | NaN | 0 | NaN | мкад | False |
| 8 | у сильвы | бар,паб | москва, ангарская улица, 42с1 | САО | ежедневно, 13:00–00:00 | 55.885528 | 37.528371 | 4.2 | выше среднего | Средний счёт:1500 ₽ | 1500.0 | NaN | 0 | NaN | ангарская улица | False |
| 9 | дом обеда | столовая | москва, улица бусиновская горка, 2 | САО | пн-пт 08:30–18:30; сб 10:00–20:00 | 55.885890 | 37.493264 | 4.1 | средние | Средний счёт:300–500 ₽ | 400.0 | NaN | 0 | 180.0 | улица бусиновская горка | False |
| 10 | база стритфуд | кафе | москва, базовская улица, 15, корп. 8 | САО | ежедневно, 10:00–23:00 | 55.877859 | 37.507754 | 4.2 | средние | Средний счёт:140–350 ₽ | 245.0 | NaN | 0 | NaN | базовская улица | False |
| 11 | чайхана беш-бармак | ресторан | москва, ленинградское шоссе, 71б, стр. 2 | САО | ежедневно, круглосуточно | 55.876908 | 37.449876 | 4.4 | средние | Средний счёт:350–500 ₽ | 425.0 | NaN | 0 | 96.0 | ленинградское шоссе | True |
| 12 | час-пик | столовая | москва, коровинское шоссе, 30а | САО | ежедневно, 09:00–21:00 | 55.884651 | 37.517482 | 4.3 | средние | Средний счёт:200–300 ₽ | 250.0 | NaN | 0 | 25.0 | коровинское шоссе | False |
| 13 | пикочино | пиццерия | москва, дмитровское шоссе, 107к2 | САО | пн-чт 11:00–22:00; пт,сб 11:00–23:00; вс 11:00–22:00 | 55.879390 | 37.541228 | 4.5 | средние | Средний счёт:300–1500 ₽ | 900.0 | NaN | 0 | NaN | дмитровское шоссе | False |
| 14 | mafe | кафе | москва, мкад, 78-й километр, 14к1 | САО | ежедневно, 10:00–19:00 | 55.893061 | 37.501319 | 3.8 | средние | Средний счёт:400–600 ₽ | 500.0 | NaN | 0 | NaN | мкад | False |
| 15 | кушай город | столовая | москва, дмитровское шоссе, 157, стр. 15 | САО | пн-пт 09:00–16:00 | 55.898414 | 37.539256 | 4.2 | средние | Средний счёт:200–250 ₽ | 225.0 | NaN | 1 | NaN | дмитровское шоссе | False |
| 16 | изба | ресторан | москва, лобненская улица, 4а | САО | ежедневно, 10:00–21:00 | 55.890030 | 37.537515 | 4.3 | средние | Средний счёт:300 ₽ | 300.0 | NaN | 0 | 40.0 | лобненская улица | False |
| 17 | виладж пицца | пиццерия | москва, базовская улица, 15, корп. 15 | САО | ежедневно, 09:00–23:00 | 55.876278 | 37.505575 | 4.3 | средние | Средний счёт:от 345 ₽ | 345.0 | NaN | 0 | NaN | базовская улица | False |
| 18 | шаурмагия | быстрое питание | москва, базовская улица, 15а | САО | ежедневно, 09:00–23:00 | 55.880831 | 37.510574 | 4.0 | средние | Средний счёт:60–400 ₽ | 230.0 | NaN | 0 | NaN | базовская улица | False |
| 19 | coffeekaldi's | кофейня | москва, угличская улица, 13, стр. 8 | СВАО | ежедневно, 09:00–22:00 | 55.900316 | 37.570558 | 4.1 | средние | Средний счёт:500–800 ₽ | 650.0 | NaN | 1 | NaN | угличская улица | False |
| lat | lng | rating | middle_avg_bill | middle_coffee_cup | chain | seats | |
|---|---|---|---|---|---|---|---|
| count | 2679.000000 | 2679.000000 | 2679.00000 | 2679.000000 | 0.0 | 2679.000000 | 1681.000000 |
| mean | 55.752948 | 37.603683 | 4.31523 | 1025.115342 | NaN | 0.338559 | 116.392029 |
| std | 0.063469 | 0.087005 | 0.30539 | 1062.633247 | NaN | 0.473308 | 121.147383 |
| min | 55.576600 | 37.357339 | 1.00000 | 0.000000 | NaN | 0.000000 | 0.000000 |
| 25% | 55.720145 | 37.548458 | 4.20000 | 400.000000 | NaN | 0.000000 | 45.000000 |
| 50% | 55.755660 | 37.602642 | 4.30000 | 850.000000 | NaN | 0.000000 | 80.000000 |
| 75% | 55.788709 | 37.654894 | 4.50000 | 1250.000000 | NaN | 1.000000 | 150.000000 |
| max | 55.920426 | 37.867228 | 5.00000 | 35000.000000 | NaN | 1.000000 | 1288.000000 |
В таблице data_drop_na_price, где мы удалили все пропуски значений среднего чека и категории уровня цен. Теперь в данных есть 4 градации уровня цен в заведении: "выше среднего", "средние", "высокие" и "низкие". При выводе на экран верха таблицы мы видим, что в категории "средние цены" есть неоднородность: ресторан -1550, кафе - 1000, пиццерия - 500 и паб, бар - 199. При этом, у нас в таблице осталось 2679 строк от 8406 - это всего лишь 31,5% данных. Данные о средней стоимости чашки каппучино удалять не стали потому что тогда останется менее 10% данных и исследование не будет достоверным.
Мы сделали это намеренно чтобы исследовать распределение цен.
# сгруппируем в очищенной от пропусков таблице данные по уровню цен -столбец 'price'
# и посчитаем среднее, медианное и их количество
data_avg_bill = data_drop_na_price.groupby('price').agg({'middle_avg_bill':['mean','median'], 'name':'count'})
data_avg_bill.columns = ['middle_avg_bill_mean', 'middle_avg_bill_median', 'count']
# округлим данные в столбце 'middle_avg_bill_mean'
data_avg_bill['middle_avg_bill_mean'] = round(data_avg_bill['middle_avg_bill_mean'],0)
# выведем данные на экран
print(data_avg_bill.head())
# построим линейный график по этой таблице
fig = px.line(data_avg_bill, x=data_avg_bill.index, y=['middle_avg_bill_mean', 'middle_avg_bill_median'], \
title='Сводная: среднее средн. чека и медианное средн. чека в зависимости от категории')
fig.show()
middle_avg_bill_mean middle_avg_bill_median count price высокие 2473.0 2000.0 437 выше среднего 1344.0 1250.0 481 низкие 217.0 180.0 93 средние 599.0 500.0 1668
# построим линейный график по этой таблице для количества заведений
fig = px.line(data_avg_bill, x=data_avg_bill.index, y=['count'], \
title='Сводная: количество заведений в зависимости от категории')
fig.show()
При этом мы видим, что медианные и средние цены не совпадают даже в этом небольшом количестве полностью заполненных данных. Медианная цена во всех ценовых категориях ниже чем средняя.
суммарное количество заведений с высокими и выше среднего ценами почти в 2 раза ниже количества заведений со средними ценами.
Построим ящики с усами по категориям заведений и по округам города Москвы и посмотрим совпадают ли медианы?
#строим ящик с усами по категориям заведений и округам
plt.figure(figsize=(18, 13))
# ограничиваем ось y для наглядности
plt.ylim(0, 4000)
plt.grid()
ax = sns.boxplot(x='category', y='middle_avg_bill', hue='district', data=data)
plt.title('Распределение среднего чека в зависимости от места расположения(округ)')
plt.show()
Медианы среднего чека отличаются по округам и категориям заведений. При этом мы определили что Центральный административный округ исказит картину и поэтому далее не берем его в расчет.
Построим ящик с усами по округам и категориям цен в заведаниях.
# строим ящик с усами по категориям заведений и округам, без ЦАО
plt.figure(figsize=(15, 10))
#ограничим верхний предел для наглядности
plt.ylim(0, 4000)
data2 = data.query('district != "ЦАО"')
ax = sns.boxplot(x='price', y='middle_avg_bill', hue='district', data=data2)
plt.grid()
plt.title('Распределение среднего чека в зависимости от места расположения(округ), без ЦАО')
plt.show()
Медианы не совпадают и в различных категориях цен в разных округах.
Возьмем категорию средние цены, категория заведения - сетевая кофейня в ЦАО, и построим гистограмму распределения среднего чека.
# проверим распределение на гистограмме распределение средниего чека в категории средние цены сетевой кофейни ЦАО
data_mean_price = data.query('price == "средние" & category == "кофейня" & chain == 1 & district == "ЦАО"')
data_mean_price.head()
| name | category | address | district | hours | lat | lng | rating | price | avg_bill | middle_avg_bill | middle_coffee_cup | chain | seats | street | is_24/7 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1870 | кофемания | кофейня | москва, лесная улица, 20, стр. 3 | ЦАО | пн-пт 09:00–22:00; сб,вс 10:00–22:00 | 55.780074 | 37.592099 | 4.4 | средние | Средний счёт:700–1400 ₽ | 1050.0 | NaN | 1 | 500.0 | лесная улица | False |
| 1955 | raw to go | кофейня | москва, лесная улица, 20, стр. 3 | ЦАО | ежедневно, 10:00–23:00 | 55.779737 | 37.593012 | 4.4 | средние | Средний счёт:500–1000 ₽ | 750.0 | NaN | 1 | 500.0 | лесная улица | False |
| 1965 | пан запекан | кофейня | москва, новослободская улица, 16 | ЦАО | ежедневно, 07:30–22:00 | 55.781715 | 37.599096 | 4.2 | средние | Средний счёт:250–400 ₽ | 325.0 | NaN | 1 | 343.0 | новослободская улица | False |
| 2202 | udcкафе | кофейня | москва, проспект мира, 26, стр. 1 | ЦАО | пн-пт 08:00–23:00; сб,вс 09:00–23:00 | 55.777489 | 37.632838 | 4.3 | средние | Средний счёт:700–1000 ₽ | 850.0 | NaN | 1 | 200.0 | проспект мира | False |
| 2364 | шоколадница | кофейня | москва, проспект мира, 29 | ЦАО | ежедневно, 08:00–23:00 | 55.779908 | 37.632628 | 4.1 | средние | Цена чашки капучино:239–274 ₽ | NaN | 256.0 | 1 | 64.0 | проспект мира | False |
# построим гистограмму распределения среднего чека в категории средних цен
plt.figure(figsize=(15, 10))
plt.hist(data_mean_price['middle_avg_bill'], color = 'yellow', edgecolor = 'black', bins=30, range=(50,3000))
plt.xlabel('средний чек')
plt.grid(True)
plt.ylabel('Количествово')
plt.title('гистограмма распределения среднего чека в категории средних цен')
plt.xticks(color='black', fontweight='bold', fontsize='16', horizontalalignment='right')
plt.show()
Проверили распределение на гистограмме распределение средниего чека в категории средние цены и категория сетевая кофейня в ЦАО. На гистограмме есть пики на уровне 260 и 700 рублей за средний чек. Еще одно подтверждение правильности незаполнеия пропусков 'middle_avg_bill'.
Медиана сильно отличается и в заведениях различных ценовых категорий. Делаем вывод - заполнять пропуски в стролбце "middle_avg_bill" некорректно. Даже усеченные данные (без пропусков значений) не дают однозначной картины - чем заполнить пропуски.
Мы исследовали только полные данные составляющие 31,5% и получили неоднородность как по категориям заведений, так и по категории цен "price" столбец в сочетании с округами Москвы. Следовательно распределение цен на рынке общественного питания зависит от слишком многих факторов и заполнение пропусков столбца "middle_avg_bill" невозможно.
По поводу заполнения пропусков столбца "price" - определить к какой категории цен принадлежит заведение сложно в разных районах и разных категориях заведений могут быть разные цены, поэтому тоже оставим как есть.
По поводу столбца "seats" - количество посадочных мест - пропуски это может быть как незаполненность данных, так и заведения с полным отсутствием посадочных мест, поэтому этот столбец тоже оставим "как есть".
Однако уже понятна ставка большей части заведений в Москве - невысокий средний чек и большое число посетителей
Исследуем количество объектов общественного питания по категориям:
Ответим на вопрос о распределении заведений по категориям.
# группируем данные по категориям и считаем количество заведений
category_name = data.groupby('category')['name'].count().sort_values(ascending=False).reset_index()
category_name.columns = ['category', 'count']
category_name
| category | count | |
|---|---|---|
| 0 | кафе | 2378 |
| 1 | ресторан | 2043 |
| 2 | кофейня | 1413 |
| 3 | бар,паб | 765 |
| 4 | пиццерия | 633 |
| 5 | быстрое питание | 603 |
| 6 | столовая | 315 |
| 7 | булочная | 256 |
# построим круговую диаграмму распределения с процентами по категориям
go.Figure(data=[go.Pie(labels=category_name['category'], \
values= category_name['count'])], \
layout = go.Layout(title=go.layout.Title(text='Круговая диаграмма категорий заведений')))
# построим столбчатую диаграмму
# используем стиль white из библиотеки seaborn
sns.set_style('white')
# назначаем размер графика
plt.figure(figsize=(15, 5))
# строим столбчатый график средствами seaborn
sns.barplot(x='category', y='count', data=category_name)
# формируем заголовок графика и подписи осей средствами matplotlib
plt.title('Столбчатая диаграмма категорий заведений')
plt.xlabel('категория заведения')
plt.ylabel('количество заведений')
# поворачиваем подписи значений по оси X на 45 градусов
plt.xticks(rotation=45)
# добавляем сетку
plt.grid()
# отображаем график на экране
plt.show()
Мы видим, что в первичных данных больше всего кафе - на них приходится 28,3% заведений, это более четверти от общего количества, на второрм месте идут рестораны - 24,3%. На третьем месте - кофейни 16,8%, далее следуют бар, паб 9,1%, пиццерия 7,53%, быстрое питание 7,17% и с небольшими долями столовые 3,75% и булочные 3,05%.
Причем на заведения длительного пребывавания (кафе и рестораны) приходится чуть больше половины 52,6%. Остальную часть рынка занимают заведения быстрого питания, еда на вынос и заведения с барными стойками. Если учесть что мы рассматриваем данные лета 2022 года, а до этого были самоизоляция и ограничения в работе заведений общественного питания. Разрешена была работа "на вынос" и доставка, и продолжалось это с марта 2020 года до марта 2022 года, то разумно предположить что из-за ограничений в работе заведения общественного питания длительного пребывания просто разорились и не выдержали конкуренции с предприятиями небольшого формата и большей мобильности. Если освобождаются места на рынке их должен кто-то заполнить. Есть предположение что из маленького формата пиццерий, кофеен и баров/пабов выростают вполне себе большие рестораны и кафе
Исследуем количество посадочных мест в заведениях по категориям:
бары и так далее.
Построим визуализации.
Проанализируем результаты и сделаем выводы.
# сгруппируем данные столбца категории по количеству посадочных мест и выведем описание таблицы
data_category_seats = data.groupby('category')['seats'].describe().reset_index()
data_category_seats = data_category_seats.round(0).sort_values(by='50%', ascending=False)# округляем значения до целых чисел
data_category_seats
| category | count | mean | std | min | 25% | 50% | 75% | max | |
|---|---|---|---|---|---|---|---|---|---|
| 6 | ресторан | 1270.0 | 122.0 | 124.0 | 0.0 | 48.0 | 86.0 | 150.0 | 1288.0 |
| 0 | бар,паб | 468.0 | 125.0 | 145.0 | 0.0 | 48.0 | 82.0 | 150.0 | 1288.0 |
| 4 | кофейня | 751.0 | 111.0 | 128.0 | 0.0 | 40.0 | 80.0 | 144.0 | 1288.0 |
| 7 | столовая | 164.0 | 100.0 | 123.0 | 0.0 | 40.0 | 76.0 | 117.0 | 1200.0 |
| 2 | быстрое питание | 349.0 | 99.0 | 107.0 | 0.0 | 28.0 | 65.0 | 140.0 | 1040.0 |
| 3 | кафе | 1218.0 | 98.0 | 118.0 | 0.0 | 35.0 | 60.0 | 120.0 | 1288.0 |
| 5 | пиццерия | 427.0 | 94.0 | 112.0 | 0.0 | 30.0 | 55.0 | 120.0 | 1288.0 |
| 1 | булочная | 148.0 | 89.0 | 98.0 | 0.0 | 25.0 | 50.0 | 120.0 | 625.0 |
# посмотрим что за заведения в данных с максимальным количеством посадочных мест 1288
data1[data1['seats'] == 1288]
| name | category | address | district | hours | lat | lng | rating | price | avg_bill | middle_avg_bill | middle_coffee_cup | chain | seats | street | is_24/7 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 6518 | delonixcafe | ресторан | москва, проспект вернадского, 94, корп. 1 | Западный административный округ | ежедневно, круглосуточно | 55.652577 | 37.475730 | 4.1 | высокие | Средний счёт:1500–2000 ₽ | 1750.0 | NaN | 0 | 1288.0 | проспект вернадского | True |
| 6524 | ян примус | ресторан | москва, проспект вернадского, 121, корп. 1 | Западный административный округ | пн-чт 12:00–00:00; пт,сб 12:00–02:00; вс 12:00–00:00 | 55.657166 | 37.481519 | 4.5 | выше среднего | Средний счёт:1500 ₽ | 1500.0 | NaN | 1 | 1288.0 | проспект вернадского | False |
| 6574 | мюнгер | пиццерия | москва, проспект вернадского, 97, корп. 1 | Западный административный округ | пн-пт 08:00–21:00; сб,вс 10:00–21:00 | 55.667505 | 37.491001 | 4.8 | NaN | NaN | NaN | NaN | 1 | 1288.0 | проспект вернадского | False |
| 6641 | one price coffee | кофейня | москва, проспект вернадского, 84, стр. 1 | Западный административный округ | ежедневно, 08:30–20:00 | 55.665129 | 37.478635 | 4.3 | NaN | NaN | NaN | NaN | 1 | 1288.0 | проспект вернадского | False |
| 6658 | гудбар | бар,паб | москва, проспект вернадского, 97, корп. 1 | Западный административный округ | пн-пт 11:00–23:00; сб,вс 13:00–23:00 | 55.667327 | 37.490601 | 4.1 | средние | Средний счёт:700 ₽ | 700.0 | NaN | 0 | 1288.0 | проспект вернадского | False |
| 6684 | пивной ресторан | бар,паб | москва, проспект вернадского, 121, корп. 1 | Западный административный округ | unknown | 55.657133 | 37.481508 | 4.5 | NaN | NaN | NaN | NaN | 0 | 1288.0 | проспект вернадского | False |
| 6690 | японская кухня | ресторан | москва, проспект вернадского, 121, корп. 1 | Западный административный округ | unknown | 55.657255 | 37.481547 | 4.4 | NaN | NaN | NaN | NaN | 1 | 1288.0 | проспект вернадского | False |
| 6771 | точка | кафе | москва, проспект вернадского, 84, стр. 1 | Западный административный округ | unknown | 55.665634 | 37.477830 | 4.7 | NaN | NaN | NaN | NaN | 1 | 1288.0 | проспект вернадского | False |
| 6807 | loft-cafe академия | кафе | москва, проспект вернадского, 84, стр. 1 | Западный административный округ | пн-пт 09:00–20:00; сб 09:00–16:00 | 55.665142 | 37.478603 | 3.6 | NaN | NaN | NaN | NaN | 0 | 1288.0 | проспект вернадского | False |
| 6808 | яндекс лавка | ресторан | москва, проспект вернадского, 51, стр. 1 | Западный административный округ | ежедневно, круглосуточно | 55.672580 | 37.507753 | 4.0 | NaN | NaN | NaN | NaN | 1 | 1288.0 | проспект вернадского | True |
| 6838 | alternative coffee | кофейня | москва, проспект вернадского, 41, стр. 1 | Западный административный округ | пн-пт 09:00–21:00; сб,вс 09:00–22:00 | 55.673128 | 37.502992 | 4.3 | NaN | NaN | NaN | NaN | 0 | 1288.0 | проспект вернадского | False |
Что удивительно - все эти заведения сосредоточены в ЗАО, в основном это кафе и рестораны, но также есть 2 кофейни и одна пиццерия. Отсюда вывод - в ЗАО сосредоточены заведения-гиганты общественного питания.
Лидируют по медианному значению посадочных мест - рестораны 86, чуть меньше медианное значение у баров/пабов (82), на третьем месте кофейни - 80 посадочных мест и четверку лидеров замыкает столовая - 76 посадочных мест.
Похоже кофейный бизнес идет хорошо количество посадочных мест догоняет рестораны и пабы и, что удивительно - кофейни перегнали столовые! Еще одна аномалия - бары/пабы по среднему количеству посадочных мест обгоняют рестораны.
Похоже что на рынке происходит размывание границ категорий заведений: пабы становятся настолько большими что вмещают посетителей как рестораны, кофейни обгоняют по посадочным местам кафе и заведения быстрого питания.
В целом медианное количество посадочных мест в заведениях Москвы от 50 до 86.
Построим боксплот с этими данными и еще один с разделением на сетевые и не сетевые заведения
# построим ящики с усами количество посадочных мест по категориям
plt.figure(figsize=(16, 10))
plt.ylim(0, 160)
data_category_seats_box=data
plt.grid()
ax = sns.boxplot(x='category', y='seats', data=data_category_seats_box)
Тройка категорий-лидеров по посадочным местам: ресторан, бар/паб и кофейня.
# построим ящики с усами количество посадочных мест по категориям сетевые/не сетевые
plt.figure(figsize=(16, 10))
plt.xlim(0, 400)
data_category_seats_box=data
plt.grid()
ax = sns.boxplot(x='seats', y='category', hue='chain', data=data_category_seats_box)
В кафе, ресторанах, кофейнях, быстром питании и столовых в сетевых заведениях медиана посадочных мест больше. В булочных медиана одинаковая вне принадлежности к сетям, в барах/пабах и пиццериях не сетевых медиана посадочных мест больше.
Максимальная разница в медиане посадочных мест между сетевым и несетевым заведением в категории кофейня.
Сетевая кофейня даст приблизительно +30 посадочных мест.
В Любом случае медиана посадочных мест во всех заведениях Москвы не превышает 100.
Это означает что большинство заведений на рынке среднего формата по посадочным местам. Свадьбу или корпоратив небольшой фирмы провести можно, а вот накормить какое-нибудь большое мероприятие - вряд ли.
# посчитаем количество сетевых и не сетевых заведений в датасете без хинкальных
display(data_without_noname.groupby('chain')['name'].count().reset_index())
| chain | name | |
|---|---|---|
| 0 | 0 | 4907 |
| 1 | 1 | 3124 |
# посчитаем количество сетевых и не сетевых заведений в общем датасете
display(data.groupby('chain')['name'].count().reset_index())
| chain | name | |
|---|---|---|
| 0 | 0 | 5201 |
| 1 | 1 | 3205 |
# построим визуализацию распределения сетевых-не сетевых заведений по количеству в первоначальном датасете, используем go.Pie
data.groupby(['chain'])['name'].count().plot(kind='pie',
figsize=(12,8),
autopct='%.1f',#проценты
labeldistance=None)
plt.title('Cоотношение сетевых и несетевых заведений по количеству (%)', fontsize=18)
plt.legend(labels = ['несетевое заведение','сетевое заведение'] )
plt.ylabel('')
plt.show()
Сетевых заведений почти в 2 раза меньше чем не сетевых (или 1/3 и 2/3) в общем датасете. Это и понятно открыть франшизу и пользоваться всеми привилегиями сети дороже чем быть пионером и открывать собственное заведение.
# Сгруппируем данные в датасете по категориям и по принадлежности к сети
data_category_chain = data.pivot_table(index='category', columns='chain', values='name', aggfunc='count')
# переименуем столбцы
data_category_chain = data_category_chain.rename(columns={1:'chain', 0:'no_chain'}).reset_index()
data_category_chain.columns = [ 'category', 'no_chain', 'chain']
data_category_chain = data_category_chain.sort_values(by='no_chain', ascending=False)
# выведем на экран
display(data_category_chain)
| category | no_chain | chain | |
|---|---|---|---|
| 3 | кафе | 1599 | 779 |
| 6 | ресторан | 1313 | 730 |
| 4 | кофейня | 693 | 720 |
| 0 | бар,паб | 596 | 169 |
| 2 | быстрое питание | 371 | 232 |
| 5 | пиццерия | 303 | 330 |
| 7 | столовая | 227 | 88 |
| 1 | булочная | 99 | 157 |
# построим визуализацию распределения сетевых заведений по категориям из общего датасета, используем go.Pie
go.Figure(data=[go.Pie(labels=data_category_chain['category'], \
values= data_category_chain['chain'])], \
layout = go.Layout(title=go.layout.Title(text='Круговая диаграмма сетевых категорий заведений')))
# построим визуализацию распределения не сетевых заведений по категориям в общем датасете, используем go.Pie
go.Figure(data=[go.Pie(labels=data_category_chain['category'], \
values= data_category_chain['no_chain'])], \
layout = go.Layout(title=go.layout.Title(text='Круговая диаграмма не сетевых категорий заведений')))
# построим визуализацию распределения не сетевых заведений по категориям из общего датасета, используем barplot
plt.figure(figsize=(10, 5))
sns.barplot(x=data_category_chain['category'], y=data_category_chain['no_chain'], data=data_category_chain.sort_values(by='no_chain'))
plt.title('Столбчатая диаграмма не сетевых заведений по категориям', fontsize=18)
plt.xlabel('Категории')
plt.ylabel('количество')
plt.grid()
plt.show()
# построим визуализацию распределения сетевых заведений по категориям из общего датасета, используем go.Pie
go.Figure(data=[go.Pie(labels=data_category_chain['category'], \
values= data_category_chain['chain'])], \
layout = go.Layout(title=go.layout.Title(text='Круговая диаграмма сетевых категорий заведений')))
data_category_chain = data_category_chain.sort_values(by='chain', ascending=False)
# построим визуализацию распределения сетевых заведений по категориям, используем barplot
plt.figure(figsize=(10, 5))
sns.barplot(x=data_category_chain['category'], y=data_category_chain['chain'], data=data_category_chain)
plt.title('Столбчатая диаграмма сетевых заведений по категориям', fontsize=18)
plt.xlabel('Категории')
plt.ylabel('количество')
# поворачиваем подписи значений по оси X на 45 градусов
plt.xticks(rotation=45)
# добавляем сетку
plt.grid()
plt.show()
Если же рассмотреть в разрезе сетевое-не сетевое заведение, то
не сетевых по отношению к средним значениям по категориям:
Если в несетевых заведениях лидируют кафе, рестораны и кофейни(кафе существенно больше), то в сетевых - доли кафе, ресторанов и кофеен практически одинаковые. Значит открыть сетевое заведение - ресторан, кафе или кофейню выбирают равное количество инвесторов, вопрос видимо в уровне вложений - у кого сколько есть.
Булочных, пиццерий и кофеен сетевых больше чем не сетевых. И наоборот кафе, ресторанов и пабов/баров больше не сетевых.
Очевидный вывод: кофейни чаще открывают сетевые.
Сгруппируем данные по названиям заведений и найдем топ-15 популярных сетей в Москве.
Под популярностью понимается количество заведений этой сети в регионе. Построим подходящую для такой информации визуализацию.
Ответим на вопросы:
Знакомы ли нам эти сети?
Есть ли какой-то признак, который их объединяет?
К какой категории заведений они относятся?
data_without_noname = data.query('name not in ["кафе", "ресторан", "хинкальная", "шаурма", "чайхана", "столовая"]').reset_index()
data_top_chain = data_without_noname['name'].value_counts().head(15).reset_index()
data_top_chain.columns=['name', 'count']
plt.figure(figsize=(10, 7))
sns.barplot(x='count', y='name', data=data_top_chain)
plt.title('Топ-15 сетей по количеству заведений общественного питания', fontsize=18)
plt.xlabel('Количество заведений')
plt.ylabel(None)
# добавляем сетку
plt.grid()
data_top_chain
| name | count | |
|---|---|---|
| 0 | шоколадница | 120 |
| 1 | домино'с пицца | 77 |
| 2 | додо пицца | 74 |
| 3 | one price coffee | 72 |
| 4 | яндекс лавка | 69 |
| 5 | cofix | 65 |
| 6 | prime | 50 |
| 7 | кофепорт | 42 |
| 8 | кулинарная лавка братьев караваевых | 39 |
| 9 | теремок | 38 |
| 10 | cofefest | 32 |
| 11 | буханка | 32 |
| 12 | му-му | 27 |
| 13 | drive café | 24 |
| 14 | кофемания | 23 |
Итого в топе по количеству заведений - больше всего 7 - категория кофейня, 1 булочная, 2 пиццерии, 2 ресторана и 3 кафе. Следовательно в топе 7 кофеен из 15 это 47%. Значит, кофейни лидируют в топе заведений общественного питания Москвы. Также видно что из маленьких заведений кофейни и булочные "дорастают" до настоящих пабов/баров, кафе и ресторанов. Всё течет всё меняется, бизнес не стоит на месте. Скорее всего очевиден факт уменьшения доли крупных заведений в ресторанном бизнесе и заполнение образовавшегося места на рынке сетями кофеен и булочных, которые в период самоизоляции освоили онлайн формат и еду на вынос и быстро перестроились. Странно что в топе нет чебуречных и шаурмы, но, скорее всего они либо не попали в датасет из-за формата подачи информации, также мы отсекли их как no name в начале исследования.
Да это всё известные сети, часто их можно встретить возле станций метро в Москве. Prime раньше была сетью кофеен из Ижевска еще год назад она была самой крупной сетью с 850 заведениями по всей России, а теперть они позиционируют себя как бар/паб. Лавка Братьев Караваевых явно была булочной, а теперь ресторан. Яндекс лавка была доставкой продуктов, а теперь пиццерия. Самая распространенная сеть в Москве - это Шоколадница - скорее всего эта сеть консолидировано решала проблемные вопросы в кризис, хотя раньше их франшиза называлась ресторан, а у нас в датасете - это кафе, возможно их маркетологи пришли к выводу что сокращение формата - это путь к выживанию.
Появление Яндекс.Лавки в топе заведений очень странное, ведь это не совсем пиццерия.
Признак который объединяет эти заведения - почти все они малого формата. В целом в топе много заведений, ориентированных на невысокий средний чек и большую проходимость
Отобразим общее количество заведений и количество заведений каждой категории по районам*. Попробуем проиллюстрировать эту информацию одним графиком.*
# посчитаем количество заведений по округам
data_district_uni = data['district'].value_counts().reset_index()
data_district_uni['share'] = round(100*data_district_uni['district']/total,0)# добавим столбец доли заведений к общему количеству
data_district_uni.columns = ['district', 'count','share']# переименуем столбцы
display('список районов и количество заведений в нем всего', data_district_uni)
display('общее количество заведений - ', data['district'].value_counts().sum())
# отберем только сетевые заведения и проделаем тоже самое
data3=data.query('chain==1')
data_district_uni_chain = data3['district'].value_counts().reset_index()
data_district_uni_chain.columns = ['district', 'count_chain']
total_chain = data_district_uni_chain['count_chain'].sum()
data_district_uni_chain['share_chain'] = round(100*data_district_uni_chain['count_chain']/total_chain,0)
# отберем только не сетевые заведения и проделаем тоже самое
data2=data.query('chain==0')
data_district_uni_nochain = data2['district'].value_counts().reset_index()
data_district_uni_nochain.columns = ['district', 'count_nochain']
total_nochain = data_district_uni_nochain['count_nochain'].sum()
data_district_uni_nochain['share_nochain'] = 100*round(data_district_uni_nochain['count_nochain']/total_nochain,2)
# создадим финальную таблицу долей заведений по округам, включим в нее данные по сетевым и несетевым заведениям
data_district_uni_final = data_district_uni.merge(data_district_uni_chain, on='district', how='left')
data_district_uni_final = data_district_uni_final.merge(data_district_uni_nochain, on='district', how='left')
data_district_uni_final = data_district_uni_final[['district', 'share', 'share_chain', 'share_nochain']]
data_district_uni_final
'список районов и количество заведений в нем всего'
| district | count | share | |
|---|---|---|---|
| 0 | ЦАО | 2242 | 27.0 |
| 1 | САО | 900 | 11.0 |
| 2 | ЮАО | 892 | 11.0 |
| 3 | СВАО | 891 | 11.0 |
| 4 | ЗАО | 851 | 10.0 |
| 5 | ВАО | 798 | 9.0 |
| 6 | ЮВАО | 714 | 8.0 |
| 7 | ЮЗАО | 709 | 8.0 |
| 8 | СЗАО | 409 | 5.0 |
'общее количество заведений - '
8406
| district | share | share_chain | share_nochain | |
|---|---|---|---|---|
| 0 | ЦАО | 27.0 | 27.0 | 26.0 |
| 1 | САО | 11.0 | 11.0 | 11.0 |
| 2 | ЮАО | 11.0 | 10.0 | 11.0 |
| 3 | СВАО | 11.0 | 10.0 | 11.0 |
| 4 | ЗАО | 10.0 | 12.0 | 9.0 |
| 5 | ВАО | 9.0 | 9.0 | 10.0 |
| 6 | ЮВАО | 8.0 | 7.0 | 9.0 |
| 7 | ЮЗАО | 8.0 | 9.0 | 8.0 |
| 8 | СЗАО | 5.0 | 5.0 | 5.0 |
Мы видим, что сетевых заведений в ЮАО, СВАО и ЮВАО падает на 1%, а в ЗАО растет на 2%, ЮЗАО растет на 1%.
И наоборотв несетевых заведений доля растет на 1% в ВАО и ЮВАО. а в ЦАО и ЗАО падает на 1%.
Можно предположить что в ЮАО, СВАО и ЮВАО - дефицит сетевых заведений общественного питания, а в ЗАО и ЮЗАО их слишком много.
А не сетевые заведения сосредоточены в ВАО и ЮВАО, а в ЦАО и ЗАО их, вполне вероятно, не хватает.
# создадим сводную таблицу округ-категория-количество заведений
data_district_category = data.pivot_table(index=['district', 'category'], values='name',aggfunc='count')
data_district_category.columns = ['count']
data_district_category = data_district_category.reset_index()
data_district_category.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 72 entries, 0 to 71 Data columns (total 3 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 district 72 non-null object 1 category 72 non-null object 2 count 72 non-null int64 dtypes: int64(1), object(2) memory usage: 1.8+ KB
# построим визуализацию распределения категорий по округам Москва, используем barplot
plt.figure(figsize=(18, 12))
sns.barplot(x='count', y=data_district_category['district'], hue=data_district_category['category'],
data=data_district_category)
plt.title('Распределение количества заведений общественного питания по округам Москвы', fontsize=18)
plt.xlabel('Количество заведений')
plt.ylabel('округ')
# выбираем положение легенды и указываем размер шрифта
plt.legend(loc='lower right', fontsize=15)
# добавляем сетку
plt.grid()
plt.show()
Во всех округах города Москвы кроме Центрального лидируют кафе. В центральном - рестораны. На втором месте во всех округах кроме Центрального и Северного - рестораны. В Центральном на втором месте кафе, в Северном - кофейни. Во всех округах кроме Северного на третьем месте кофейни. В Северном - рестораны. На последнем месте по распространенности - булочные во всех округах кроме ЗАО и ЮЗАО - там они на предпоследнем месте, на последнем - столовые.
Визуально больше всего заведений общественного питания в Центральном округе, меньше всего - в Северо-Западном.
Если предположить что количество кофеен может "дорасти" до количества кафе как в ЦАО, то Хуже всего потенциал для роста числа кофеен в ЦАО, СЗАО(там мало заведений вообще) и в САО. Лучше всего потенциал ЮВАО и ВАО, на втором месте по возможности роста ЮЗАО и ЮАО. В остальных округах просто есть потенциал для открытия кофеен.
Сильно ли различаются усреднённые рейтинги в разных типах общепита?
# создаем сгруппированную таблицу категрия-уровень цен-рейтинг(медианное значение)
a = data.groupby(['category', 'price'], as_index = False)[['rating']].median()
# используем стиль white из библиотеки seaborn
sns.set_style('white')
# назначаем размер графика
plt.figure(figsize=(15, 5))
# строим столбчатый график средствами seaborn
sns.barplot(x='category', y='rating', data=a, hue='price')
# формируем заголовок графика и подписи осей средствами matplotlib
plt.title('График зависимости рейтинга заведения от категории заведения и уровня цен')
plt.xlabel('категория заведения')
plt.ylabel('рейтинг')
# поворачиваем подписи значений по оси X на 45 градусов
plt.xticks(rotation=45)
# выбираем положение легенды и указываем размер шрифта
plt.legend(loc='upper right', fontsize=10)
plt.ylim(3.5, 4.8)
# добавляем сетку
plt.grid()
# отображаем график на экране
plt.show()
Низкие цены портят рейтинги в барах/пабах, быстром питании, кафе, пиццериях и ресторанах. Цены выше среднего портят рейтинги столовых. Средние цены снижают рейтинг булочных.
Самый высокий средний рейтинг 4,5 в ресторанах с высокими ценами - оно и понятно, скорее всего часть выручки рестораны тратят на оборудование, сотрудников и интерьер, а это благотворно сказывается на рейтинге.
Самый низкий средний рейтинг в барах/пабах с низкими ценами 3,9 - страшно даже предположить что за публика туда ходит. В быстром питании с низкими ценами и столовых с выше среднего ценами средний рейтинг тоже низкий 4,0
Самый однородный средний рейтинг от 4,3 до 4,4 у кофеен, то есть какие бы цены не были у кофеен, ее рейтинг будет достаточно высоким.
# группируем данные по категориям и округам, считаем медианный рейтинг
a = data.groupby(['category', 'district'], as_index = False)[['rating']].median()
# используем стиль white из библиотеки seaborn
sns.set_style('white')
# назначаем размер графика
plt.figure(figsize=(15, 7))
# строим столбчатый график средствами seaborn
sns.barplot(x='category', y='rating', data=a, hue='district')
# формируем заголовок графика и подписи осей средствами matplotlib
plt.title('График зависимости рейтинга заведения от категории заведения и округа Москвы')
plt.xlabel('категория заведения')
plt.ylabel('рейтинг')
# поворачиваем подписи значений по оси X на 45 градусов
plt.xticks(rotation=45)
# выбираем положение легенды и указываем размер шрифта
plt.legend(loc='upper right', fontsize=10)
plt.ylim(4, 4.75)
# добавляем сетку
plt.grid()
# отображаем график на экране
plt.show()
Самые высокие рейтинги у баров/пабов. Самые низкие уровни рейтингов у быстрого питания. Отностительно однородные рейтинги в кофейнях, пиццериях и ресторанах. Центральный район всегда на пункт выше имеет рейтинг почти во всех категориях.
Хуже всего со средним рейтингом у СЗАО быстрого питания 4,05. Лучше всего с рейтингом баров/пабов в ЗАО и ЦАО 4,5. Рейтинги 4,4 имеют булочные в ЦАО и ЮАО, бары/пабы в СЗАО, ЮАО, ЮВАО И ЮЗАО и рестораны и пиццерии в ЦАО.
С рейтингом кофеен хуже всего обстоит дело в ЗАО - 4,2, в остальных райнах 4,3
Границы районов Москвы, которые встречаются в датасете, хранятся в файле admin_level_geomap.geojson
rating_df = data1.groupby('district', as_index=False)['rating'].agg('median')
rating_df
# загружаем JSON-файл с границами округов Москвы
state_geo = '/datasets/admin_level_geomap.geojson'
# moscow_lat - широта центра Москвы, moscow_lng - долгота центра Москвы
moscow_lat, moscow_lng = 55.751244, 37.618423
# создаём карту Москвы
m = Map(location=[moscow_lat, moscow_lng], zoom_start=10)
# создаём хороплет с помощью конструктора Choropleth и добавляем его на карту
Choropleth(
geo_data=state_geo,
data=rating_df,
columns=['district', 'rating'],
key_on='feature.name',
fill_color='YlGn',
fill_opacity=0.8,
legend_name='Медианный рейтинг заведений по районам',
).add_to(m)
# выводим карту
m
rating_df.sort_values(by = 'rating', ascending = False)
| district | rating | |
|---|---|---|
| 5 | Центральный административный округ | 4.4 |
| 0 | Восточный административный округ | 4.3 |
| 1 | Западный административный округ | 4.3 |
| 2 | Северный административный округ | 4.3 |
| 4 | Северо-Западный административный округ | 4.3 |
| 7 | Юго-Западный административный округ | 4.3 |
| 8 | Южный административный округ | 4.3 |
| 3 | Северо-Восточный административный округ | 4.2 |
| 6 | Юго-Восточный административный округ | 4.2 |
На карте видно что лучшие рейтинги в ЦАО, худшие в СВАО и ЮВАО.
# moscow_lat - широта центра Москвы, moscow_lng - долгота центра Москвы
moscow_lat, moscow_lng = 55.751244, 37.618423
# создаём карту Москвы
m = Map(location=[moscow_lat, moscow_lng], zoom_start=10)
# создаём пустой кластер, добавляем его на карту
marker_cluster = MarkerCluster().add_to(m)
# пишем функцию, которая принимает строку датафрейма,
# создаёт маркер в текущей точке и добавляет его в кластер marker_cluster
def create_clusters(row):
Marker(
[row['lat'], row['lng']],
popup=f"{row['name']} {row['rating']}",
).add_to(marker_cluster)
# применяем функцию create_clusters() к каждой строке датафрейма
data.apply(create_clusters, axis=1)
# выводим карту
m
Меньше всего заведений на юге и юго-востоке. Больше всего в центре. Также запад Москвы плохо заполнен заведениями общественного питания.
ЦАО лидирует по всем параметрам:
Построим график распределения количества заведений и их категорий по этим улицам. Попробуем проиллюстрировать эту информацию одним графиком.
# сгруппируем данные по улицам и посчитаем на их количество заведений
group_street = data.groupby('street').agg({'name': 'count'})
group_street = group_street.rename(columns={'name':'count'}).sort_values('count', ascending=False)
group_street = group_street.reset_index()
# выведем на экран 15 улиц с самым большим количеством заведений
display(group_street.head(15))
# создадим список улиц с максимальным количеством заведений
list_street=['проспект мира', 'профсоюзная улица', 'проспект вернадского', 'ленинский проспект', 'ленинградский проспект',
'дмитровское шоссе', 'каширское шоссе', 'варшавское шоссе', 'ленинградское шоссе', 'мкад', 'люблинская улица',
'улица вавилова', 'кутузовский проспект', 'улица миклухо-маклая', 'пятницкая улица']
top15_street_ = group_street[group_street['street'].isin(list_street)]
# добавим столбец длины топ 15 улиц с максимальным количеством заведений через словарь
group_street['long'] = group_street['street']
group_street['long'].replace({'проспект мира':8.9,
'профсоюзная улица':9.3,
'проспект вернадского':8,
'ленинский проспект':14,
'ленинградский проспект':5.6,
'дмитровское шоссе':17,
'каширское шоссе':12,
'варшавское шоссе':22.5,
'мкад':108,
'люблинская улица':8.8,
'кутузовский проспект':8.3,
'улица миклухо-маклая':3.6,
'пятницкая улица':1.8,
'улица вавилова':5.5,
'ленинградское шоссе':40.9}, inplace=True)
# создадим датасет с этими улцами
top15street = group_street.head(15)
# добавим столбец - количество заведений на 1 км улицы
top15street['name_by_km'] = top15street['count']/top15street['long']
top15street['name_by_km'] = top15street['name_by_km'].astype(int)
top15street['name_by_km'] = round(top15street['name_by_km'],2)
# выведем на экран таблицу загруженности улиц заведениями общественного питания
top15street_by_km = top15street.sort_values(by='name_by_km', ascending=False)
display(top15street_by_km)
# построим горизонтальную столбчатую диаграмму улиц с максимальным количеством заведений
plt.figure(figsize=(20, 12))
barplot_top15_street_ = sns.barplot(x='count', y='street', data=top15_street_)
barplot_top15_street_.set_title('Топ-15 улиц по количеству заведений общественного питания', fontsize=18)
barplot_top15_street_.set_xlabel(None)
barplot_top15_street_.set_ylabel(None)
plt.show()
| street | count | |
|---|---|---|
| 0 | проспект мира | 184 |
| 1 | профсоюзная улица | 122 |
| 2 | проспект вернадского | 108 |
| 3 | ленинский проспект | 107 |
| 4 | ленинградский проспект | 95 |
| 5 | дмитровское шоссе | 88 |
| 6 | каширское шоссе | 77 |
| 7 | варшавское шоссе | 76 |
| 8 | ленинградское шоссе | 70 |
| 9 | мкад | 65 |
| 10 | люблинская улица | 60 |
| 11 | улица вавилова | 55 |
| 12 | кутузовский проспект | 54 |
| 13 | улица миклухо-маклая | 49 |
| 14 | пятницкая улица | 48 |
| street | count | long | name_by_km | |
|---|---|---|---|---|
| 14 | пятницкая улица | 48 | 1.8 | 26 |
| 0 | проспект мира | 184 | 8.9 | 20 |
| 4 | ленинградский проспект | 95 | 5.6 | 16 |
| 1 | профсоюзная улица | 122 | 9.3 | 13 |
| 2 | проспект вернадского | 108 | 8 | 13 |
| 13 | улица миклухо-маклая | 49 | 3.6 | 13 |
| 11 | улица вавилова | 55 | 5.5 | 10 |
| 3 | ленинский проспект | 107 | 14 | 7 |
| 6 | каширское шоссе | 77 | 12 | 6 |
| 10 | люблинская улица | 60 | 8.8 | 6 |
| 12 | кутузовский проспект | 54 | 8.3 | 6 |
| 5 | дмитровское шоссе | 88 | 17 | 5 |
| 7 | варшавское шоссе | 76 | 22.5 | 3 |
| 8 | ленинградское шоссе | 70 | 40.9 | 1 |
| 9 | мкад | 65 | 108 | 0 |
# оставим в данных только самые загруженные улицы
top15_street_data = data1[data1['street'].isin(list_street)].reset_index()
print('количество заведений на улицах с максмальным количеством заведений: ',top15_street_data.shape[0])
количество заведений на улицах с максмальным количеством заведений: 1258
print('количество заведений на улицах с максмальным количеством заведений: ', round(100*top15_street_data.shape[0]/8406,0), '%')
количество заведений на улицах с максмальным количеством заведений: 15.0 %
print('медианное количество посадочных мест на улицах с максмальным количеством заведений: ',
top15_street_data['seats'].median())
медианное количество посадочных мест на улицах с максмальным количеством заведений: 98.0
print('распределение заведений по уровню цен на улицах с максмальным количеством заведений: \n',
top15_street_data['price'].value_counts())
распределение заведений по уровню цен на улицах с максмальным количеством заведений: средние 300 выше среднего 85 высокие 72 низкие 22 Name: price, dtype: int64
print('медианный средний чек на улицах с максмальным количеством заведений: ', top15_street_data['middle_avg_bill'].median())
медианный средний чек на улицах с максмальным количеством заведений: 750.0
print('распределение заведений по категориям на улицах с максмальным количеством заведений: \n',
top15_street_data['category'].value_counts())
распределение заведений по категориям на улицах с максмальным количеством заведений: кафе 353 ресторан 327 кофейня 220 быстрое питание 117 бар,паб 91 пиццерия 84 столовая 40 булочная 26 Name: category, dtype: int64
zao = top15_street_data[top15_street_data['district']=='Западный административный округ']
print('распределение заведений по категориям на улицах с максмальным количеством заведений в ЗАО:\n ', zao['category'].value_counts())
распределение заведений по категориям на улицах с максмальным количеством заведений в ЗАО: ресторан 57 кафе 44 кофейня 32 быстрое питание 14 пиццерия 12 бар,паб 9 столовая 5 булочная 3 Name: category, dtype: int64
print('распределение заведений по округам на улицах с максмальным количеством заведений без ЦАО:\n ',
top15_street_data[top15_street_data['district'] != 'Центральный административный округ']['district'].value_counts())
распределение заведений по округам на улицах с максмальным количеством заведений без ЦАО: Юго-Западный административный округ 293 Северный административный округ 232 Южный административный округ 197 Западный административный округ 176 Северо-Восточный административный округ 162 Юго-Восточный административный округ 87 Восточный административный округ 2 Северо-Западный административный округ 1 Name: district, dtype: int64
print('распределение рейтингов на улицах с максмальным количеством заведений: \n ',
top15_street_data['rating'].value_counts().head(8))
распределение рейтингов на улицах с максмальным количеством заведений: 4.4 209 4.3 204 4.2 162 4.1 122 4.0 101 4.5 86 4.7 61 4.6 47 Name: rating, dtype: int64
print('распределение по принадлежности к сети на улицах с максмальным количеством заведений: \n ',
top15_street_data['chain'].value_counts())
распределение по принадлежности к сети на улицах с максмальным количеством заведений: 0 719 1 539 Name: chain, dtype: int64
На самых загруженных улицах:
# построим горизонтальную столбчатую диаграмму улиц с максимальным количеством заведений на 1 км
plt.figure(figsize=(20, 12))
barplot_top15_street_ = sns.barplot(x='name_by_km', y='street', data=top15street_by_km)
barplot_top15_street_.set_title('Топ-15 улиц по количеству заведений общественного питания на 1 км', fontsize=18)
barplot_top15_street_.set_xlabel('количество заедений на 1 км')
barplot_top15_street_.set_ylabel(None)
plt.show()
В топ 15 улиц с самым большим количеством заведений попали не только загруженные общепитом, но и просто очень длинные улицы. Когда мы добавили протяженность улиц и рассчитали плотность загрузки заведениями - результат ТОП 15 изменился и лидером стала Пятницкая улица на которой плотность самая высокая - 26 заведений на 1 км. А МКАД с 10-го места переместился на последнее, просто потому что его протяженость 108 км.
# группируем данные по улице и категория заведения, считаем точки питания на них
group_street = data.groupby(['street', 'category']).agg({'name': 'count'})
# переименовываем колонки
group_street = group_street.rename(columns={'name':'count'}).sort_values('count', ascending=False)
# перенумеровываем таблицу
group_street = group_street.reset_index()
# формируем список улиц с максимальным количеством заведений в одной категории топ 15
list_street=['проспект мира', 'мкад', 'профсоюзная улица', 'проспект вернадского', 'ленинский проспект', 'ленинградский проспект',
'дмитровское шоссе', 'каширское шоссе', 'варшавское шоссе', 'ленинградское шоссе', 'люблинская улица',
'улица вавилова', 'кутузовский проспект', 'улица миклухо-маклая', 'пятницкая улица']
# сортируем таблицу, оставляя только данные из этого топ листа
top15_street = group_street[group_street['street'].isin(list_street)].sort_values(by='count', ascending=False)
# выводим верх таблицы на экран
display(top15_street.head(15))
plt.figure(figsize=(20, 12))
barplot_top15_street = sns.barplot(x='street', y='count', hue='category', data=top15_street)
barplot_top15_street.set_title('Топ-15 улиц по количеству и категориям заведений общественного питания', fontsize=18)
barplot_top15_street.set_xlabel('улицы')
barplot_top15_street.set_ylabel('количество заведений')
# поворачиваем подписи значений по оси X на 45 градусов
plt.xticks(rotation=45)
# выбираем положение легенды и указываем размер шрифта
plt.legend(loc='upper right', fontsize=13)
plt.show()
| street | category | count | |
|---|---|---|---|
| 0 | проспект мира | кафе | 53 |
| 2 | проспект мира | ресторан | 45 |
| 1 | мкад | кафе | 45 |
| 3 | проспект мира | кофейня | 36 |
| 4 | профсоюзная улица | кафе | 35 |
| 5 | ленинский проспект | ресторан | 33 |
| 6 | проспект вернадского | ресторан | 33 |
| 7 | профсоюзная улица | ресторан | 26 |
| 8 | ленинградское шоссе | ресторан | 26 |
| 9 | ленинский проспект | кафе | 26 |
| 10 | люблинская улица | кафе | 26 |
| 12 | ленинградский проспект | кофейня | 25 |
| 13 | ленинградский проспект | ресторан | 25 |
| 11 | проспект вернадского | кафе | 25 |
| 14 | дмитровское шоссе | ресторан | 24 |
Безусловным лидером является проспект Мира, на нем больше всего кафе, ресторанов и кофеен.
На 7-ми улицах лидируют кафе, на 7 рестораны.
На Ленинградском проспекте кофеен и ресторанов поровну.
На МКАДе и Люблинской улице превалируют кафе.
На проспекте Вернадского и Ленинском проспекте превалируют рестораны.
Не подходят для открытия кофеен (слабый потенциал развития) Кутузовский проспект, Ленинский проспект, Ленинградское шоссе, Ленинградский проспект, Варшавское шоссе, Пятницкая улица, улица Вавилова и Каширское шоссе - здесь их открыто примерно столько же сколько кафе и ресторанов. Также не подходит МКАД из-за скоростного режима и запрета остановок.
Исследуем количество заведений по категориям и по округам г. Москвы
Посмотрим какой потенциал у кофеен по округам
# создадим сводную таблицу округ-категория-количество заведений
data_district_category = data.pivot_table(index=['district', 'category'], values='name',aggfunc='count')
data_district_category.columns = ['count']
data_district_category = data_district_category.reset_index()
data_district_category
| district | category | count | |
|---|---|---|---|
| 0 | ВАО | бар,паб | 53 |
| 1 | ВАО | булочная | 25 |
| 2 | ВАО | быстрое питание | 71 |
| 3 | ВАО | кафе | 272 |
| 4 | ВАО | кофейня | 105 |
| ... | ... | ... | ... |
| 67 | ЮЗАО | кафе | 238 |
| 68 | ЮЗАО | кофейня | 96 |
| 69 | ЮЗАО | пиццерия | 64 |
| 70 | ЮЗАО | ресторан | 168 |
| 71 | ЮЗАО | столовая | 17 |
72 rows × 3 columns
# построим визуализацию распределения категорий по округам Москва, используем barplot
plt.figure(figsize=(18, 12))
sns.barplot(x='count', y=data_district_category['district'], hue=data_district_category['category'], data=data_district_category)
plt.title('Распределение количества заведений общественного питания по округам Москвы', fontsize=18)
plt.xlabel('Количество заведений')
plt.ylabel(None)
# выбираем положение легенды и указываем размер шрифта
plt.legend(loc='lower right', fontsize=15)
# добавляем сетку
plt.grid()
plt.show()
Хороший потенциал открытия кофейни в ВАО, СВАО, ЮВАО, ЮЗАО, ЮАО. Чуть похуже - ЗАО. Центральный и СЗАО не рассматриваем - в первом максимум кофеен, во втором вообще мало заведений (скорее всего из-за отсутствия посетителей). Напоминаю, мы предположили что если в ЦАО одинаковое количество кофеен и кафе, то и в других округах есть потенциал для роста кофеен.
Что можно сказать об этих заведениях?
# посчитаем количество улиц с одинокими заведениями общественного питания
group_street = data.groupby('street').agg({'name': 'count'})
group_street = group_street.rename(columns={'name':'count'}).sort_values('count')
group_street = group_street.reset_index()
print('Число улиц с одним объектом общественного питания:', len(group_street[group_street['count']==1]))
Число улиц с одним объектом общественного питания: 457
# общий список улиц с одинокими заведениями общепита
group_street1 = group_street[group_street['count']==1]['street']
# топ улиц с одинокими заведениями выведем на экран
print('топ 15 улиц с одним заведением общественного питания')
display(group_street1.head(15))
# преобразуем столбец в список
street_1 = group_street1.tolist()
# фильтруем изначальные данные, оставляем в нем только строки с улицами на которых по одному заведению общепита
streets_name_along = data1[data1['street'].isin(street_1)].reset_index()
топ 15 улиц с одним заведением общественного питания
0 1-й автозаводский проезд 1 новосущёвская улица 2 новощукинская улица 3 новоясеневский тупик 4 одинцовская улица 5 октябрьский переулок 6 ордынский тупик 7 оренбургская улица 8 новороссийская улица 9 орловский переулок 10 островная улица 11 отрадный проезд 12 павелецкая набережная 13 панкратьевский переулок 14 парк алтуфьево Name: street, dtype: object
streets_name_along.info()
streets_name_along.head()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 457 entries, 0 to 456 Data columns (total 17 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 index 457 non-null int64 1 name 457 non-null object 2 category 457 non-null object 3 address 457 non-null object 4 district 457 non-null object 5 hours 457 non-null object 6 lat 457 non-null float64 7 lng 457 non-null float64 8 rating 457 non-null float64 9 price 182 non-null object 10 avg_bill 194 non-null object 11 middle_avg_bill 165 non-null float64 12 middle_coffee_cup 24 non-null float64 13 chain 457 non-null int64 14 seats 156 non-null float64 15 street 457 non-null object 16 is_24/7 457 non-null bool dtypes: bool(1), float64(6), int64(2), object(8) memory usage: 57.7+ KB
| index | name | category | address | district | hours | lat | lng | rating | price | avg_bill | middle_avg_bill | middle_coffee_cup | chain | seats | street | is_24/7 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 15 | дом обеда | столовая | москва, улица бусиновская горка, 2 | Северный административный округ | пн-пт 08:30–18:30; сб 10:00–20:00 | 55.885890 | 37.493264 | 4.1 | средние | Средний счёт:300–500 ₽ | 400.0 | NaN | 0 | 180.0 | улица бусиновская горка | False |
| 1 | 21 | 7/12 | кафе | москва, прибрежный проезд, 7 | Северный административный округ | ежедневно, 10:00–22:00 | 55.876805 | 37.464934 | 4.5 | NaN | NaN | NaN | NaN | 0 | NaN | прибрежный проезд | False |
| 2 | 25 | в парке вкуснее | кофейня | москва, парк левобережный | Северный административный округ | ежедневно, 10:00–21:00 | 55.878453 | 37.460028 | 4.3 | NaN | NaN | NaN | NaN | 1 | NaN | парк левобережный | False |
| 3 | 58 | coffeekaldi's | кофейня | москва, угличская улица, 13, стр. 8 | Северо-Восточный административный округ | ежедневно, 09:00–22:00 | 55.900316 | 37.570558 | 4.1 | средние | Средний счёт:500–800 ₽ | 650.0 | NaN | 1 | NaN | угличская улица | False |
| 4 | 60 | чебуречная история | кофейня | москва, ландшафтный заказник лианозовский | Северо-Восточный административный округ | ежедневно, 10:00–22:00 | 55.899845 | 37.570488 | 4.9 | NaN | NaN | NaN | NaN | 1 | NaN | ландшафтный заказник лианозовский | False |
print('распределение одиноких объектов общепита по приадлежности к сети (1-сетевые, 0-не сетевые):')
streets_name_along['chain'].value_counts()
распределение одиноких объектов общепита по приадлежности к сети (1-сетевые, 0-не сетевые):
0 324 1 133 Name: chain, dtype: int64
print('распределение одиноких объектов общепита по категориям:')
along_point = streets_name_along.groupby('category').agg({'name' : 'count'}).reset_index()
along_point = along_point.sort_values(by='name', ascending=False)
along_point
распределение одиноких объектов общепита по категориям:
| category | name | |
|---|---|---|
| 3 | кафе | 159 |
| 6 | ресторан | 93 |
| 4 | кофейня | 84 |
| 0 | бар,паб | 39 |
| 7 | столовая | 36 |
| 2 | быстрое питание | 23 |
| 5 | пиццерия | 15 |
| 1 | булочная | 8 |
# построим столбчатую диаграмму
# используем стиль white из библиотеки seaborn
sns.set_style('white')
# назначаем размер графика
plt.figure(figsize=(15, 5))
# строим столбчатый график средствами seaborn
sns.barplot(x='category', y='name', data=along_point)
# формируем заголовок графика и подписи осей средствами matplotlib
plt.title('Столбчатая диаграмма категорий одиноких заведений')
plt.xlabel('категория заведения')
plt.ylabel('количество заведений')
# поворачиваем подписи значений по оси X на 45 градусов
plt.xticks(rotation=45)
# добавляем сетку
plt.grid()
# отображаем график на экране
plt.show()
print('распределение рейтингов одиноких объектов общепита:')
streets_name_along['rating'].value_counts()
plt.figure(figsize=(15, 10))
plt.hist(streets_name_along['rating'], color = 'yellow', edgecolor = 'black', bins=30, range=(0,5))
plt.xlabel('рейтинг')
plt.grid(True)
plt.ylabel('Количествово')
plt.title('гистограмма распределения рейтинга среди одиноких заведений')
plt.xticks(color='black', fontweight='bold', fontsize='16', horizontalalignment='right')
plt.show()
распределение рейтингов одиноких объектов общепита:
print('распределение часов работы одиноких объектов общепита:')
streets_name_along['hours'].value_counts()
распределение часов работы одиноких объектов общепита:
ежедневно, круглосуточно 31
unknown 27
ежедневно, 10:00–22:00 25
ежедневно, 11:00–23:00 24
ежедневно, 12:00–00:00 20
..
пн-чт 12:00–23:00; пт-вс 11:00–00:00 1
пн-пт 06:30–00:00; сб,вс 08:00–23:00 1
пн-пт 08:00–23:00; сб,вс 10:00–22:00 1
пн-пт 08:30–19:30 1
пн-пт 09:00–23:00; сб,вс 11:00–23:00 1
Name: hours, Length: 179, dtype: int64
print('распределение по округам одиноких объектов общепита:')
streets_name_along['district'].value_counts()
распределение по округам одиноких объектов общепита:
Центральный административный округ 145 Северо-Восточный административный округ 55 Восточный административный округ 52 Северный административный округ 51 Южный административный округ 43 Юго-Восточный административный округ 39 Западный административный округ 35 Северо-Западный административный округ 19 Юго-Западный административный округ 18 Name: district, dtype: int64
# построим гистограмму распределения значений среднего чека в одиноких объектах общепита
plt.figure(figsize=(15, 10))
streets_name_along['middle_avg_bill'].hist(bins=150)
plt.grid(True)
plt.ylabel('Количествово')
plt.title('гистограмма распределения уровня среднего чека среди одиноких заведений')
plt.xticks(color='black', fontweight='bold', fontsize='16', horizontalalignment='right')
plt.show()
В одиноких заведениях есть 2 пика уровня среднего чека - на 200р и на 1600р. Скорее всего - это 2 уровня обслуживания - быстрое питание (кофе и булочка или шаурма) и второй вариант 1600р - это заведение длительного пребывания с средним чеком полноценного обеда.
# построим хороплет распределения одиноких заведений
# moscow_lat - широта центра Москвы, moscow_lng - долгота центра Москвы
moscow_lat, moscow_lng = 55.751244, 37.618423
# создаём карту Москвы
m = Map(location=[moscow_lat, moscow_lng], zoom_start=10)
# создаём пустой кластер, добавляем его на карту
marker_cluster = MarkerCluster().add_to(m)
# пишем функцию, которая принимает строку датафрейма,
# создаёт маркер в текущей точке и добавляет его в кластер marker_cluster
def create_clusters(row):
Marker(
[row['lat'], row['lng']],
popup=f"{row['name']} {row['rating']}",
).add_to(marker_cluster)
# применяем функцию create_clusters() к каждой строке датафрейма
streets_name_along.apply(create_clusters, axis=1)
# выводим карту
m
Число улиц с одним объектом общественного питания: 457.
Такие заведения общепита раскинуты практически по всей территории города и как правило располагаются вдали от основных дорог, в промзонах, рядом с парками.
Сетевых заведений на 41.8% меньше чем не сетевых.
Рейтинги обычно выше среднего от 4.0 и выше.
Больше всего улиц с одним заведением в ЦАО почти в три раза больше чем в СВАО, ВАО и САО. В остальных округах еще меньше.
Часы работы заведений 24/7. второй по популярности график 10.00 - 22.00 (самый распространенный).
Самый плохой рейтинг в ЮВАО, 3.0.
топ Категории: кафе (159), ресторан (93), кофейня (84).
.
Визуально - больше всего таких улиц с одним заведений в центре и это связано с их короткой протяженностью.
# построим дисплот и ящик с усами по количеству посадочных мест
plt.figure(figsize=(15, 8))
distplot = sns.distplot(streets_name_along['seats'])
distplot.set_title('Распределение количества посадочных мест для улиц с одним заведением общественного питания', fontsize=18)
plt.show()
fig = px.box(
streets_name_along,
y ='seats',
title='Распределение количества посадочных мест для улиц \nс одним заведением общественного питания',
height=500
)
fig.show()
# построим дисплот и ящик с усами по рейтингу
plt.figure(figsize=(15, 5))
distplot = sns.distplot(streets_name_along['rating'])
distplot.set_title('Распределение рейтинга для улиц с одним заведением общественного питания', fontsize=18)
plt.show()
fig = px.box(
streets_name_along,
y ='rating',
title='Распределение рейтинга для улиц \nс одним заведением общественного питания',
height=500
)
fig.show()
# построим дисплот и ящик с усами по среднему чеку
plt.figure(figsize=(15, 7))
distplot = sns.distplot(streets_name_along['middle_avg_bill'])
distplot.set_title('Распределение среднего чека для улиц с одним заведением общественного питания', fontsize=18)
plt.show()
fig = px.box(
streets_name_along,
y ='middle_avg_bill',
title='Распределение среднего чека для улиц \nс одним заведением общественного питания',
height=500
)
fig.show()
# сгруппируем данные об улицах с одним заведением по принадлежности к сетям
data_category_along = streets_name_along.pivot_table(index='category', columns='chain', values='name', aggfunc='count')
data_category_along = data_category_along.rename(columns={1:'chain', 0:'no_chain'}).reset_index()
# выведем на экран
data_category_along
| chain | category | no_chain | chain |
|---|---|---|---|
| 0 | бар,паб | 33 | 6 |
| 1 | булочная | 4 | 4 |
| 2 | быстрое питание | 15 | 8 |
| 3 | кафе | 116 | 43 |
| 4 | кофейня | 52 | 32 |
| 5 | пиццерия | 6 | 9 |
| 6 | ресторан | 71 | 22 |
| 7 | столовая | 27 | 9 |
# построим круговую диаграмму категорий не сетевых заведений для улиц с одним заведением
go.Figure(data=[go.Pie(labels=data_category_along['category'], \
values= data_category_along['no_chain'])], \
layout = go.Layout(title=
go.layout.Title(text=
'Круговая диаграмма категорий не сетевых заведений для улиц с одним заведением')))
# построим круговую диаграмму категорий сетевых заведений для улиц с одним заведением
go.Figure(data=[go.Pie(labels=data_category_along['category'], \
values= data_category_along['chain'])], \
layout = go.Layout(title=go.layout.Title(text='Круговая диаграмма сетевых категорий для улиц с одним заведением')))
# сгруппируем данные об улицах с одним заведением по округам
data_district_along = streets_name_along.pivot_table(index='district', columns='chain', values='name', aggfunc='count')
data_district_along = data_district_along.rename(columns={1:'chain', 0:'no_chain'}).reset_index()
data_district_along
| chain | district | no_chain | chain |
|---|---|---|---|
| 0 | Восточный административный округ | 39 | 13 |
| 1 | Западный административный округ | 25 | 10 |
| 2 | Северный административный округ | 37 | 14 |
| 3 | Северо-Восточный административный округ | 33 | 22 |
| 4 | Северо-Западный административный округ | 13 | 6 |
| 5 | Центральный административный округ | 104 | 41 |
| 6 | Юго-Восточный административный округ | 29 | 10 |
| 7 | Юго-Западный административный округ | 11 | 7 |
| 8 | Южный административный округ | 33 | 10 |
# построим круговую диаграмму по округам не сетевых заведений для улиц с одним заведением
go.Figure(data=[go.Pie(labels=data_district_along['district'], \
values= data_district_along['no_chain'])], \
layout = go.Layout(title=go.
layout.Title(text=
'Круговая диаграмма по округам не сетевых заведений для улиц с одним заведением')))
# построим круговую диаграмму по округам сетевых заведений для улиц с одним заведением
go.Figure(data=[go.Pie(labels=data_district_along['district'], \
values= data_district_along['chain'])], \
layout = go.Layout(title=go.layout.Title(text='Круговая диаграмма по округам сетевых заведений для улиц с одним заведением')))
У единственных заведений на улице
Значения средних чеков заведений хранятся в столбце middle_avg_bill. Эти числа показывают примерную стоимость заказа в рублях, которая чаще всего выражена диапазоном. Посчитаем медиану этого столбца для каждого района. Используем это значение в качестве ценового индикатора района.
# сгруппируем данные по округам, уровню цен и принадлежности к сети
b = data.groupby(['district', 'price','chain'], as_index = False)[['middle_avg_bill']].median()#.sort_values(by='price')
display(b.sort_values(by='middle_avg_bill', ascending=False).head(15))
# построим столбчатую диаграмму
fig = px.bar(b, x='district', y='middle_avg_bill', color='price', title='среднее среднего чека по категориям и округам')
fig.update_xaxes(tickangle=45)
fig.show()
| district | price | chain | middle_avg_bill | |
|---|---|---|---|---|
| 55 | ЮВАО | высокие | 0 | 2750.0 |
| 47 | ЮАО | высокие | 0 | 2250.0 |
| 39 | ЦАО | высокие | 0 | 2250.0 |
| 8 | ЗАО | высокие | 0 | 2250.0 |
| 24 | СВАО | высокие | 0 | 2125.0 |
| 16 | САО | высокие | 0 | 2100.0 |
| 1 | ВАО | высокие | 1 | 2000.0 |
| 40 | ЦАО | высокие | 1 | 2000.0 |
| 32 | СЗАО | высокие | 0 | 2000.0 |
| 0 | ВАО | высокие | 0 | 2000.0 |
| 9 | ЗАО | высокие | 1 | 2000.0 |
| 63 | ЮЗАО | высокие | 1 | 2000.0 |
| 62 | ЮЗАО | высокие | 0 | 2000.0 |
| 25 | СВАО | высокие | 1 | 1875.0 |
| 48 | ЮАО | высокие | 1 | 1875.0 |
Больше всего денег в ЦАО, далее следует ЗАО и ВАО. Меньше всего денег в ЮВАО.
Как удалённость от центра влияет на цены в заведениях?
# построим столбчатую диаграмму по медианам среднего чека по округам Москвы
a_1 = data.groupby(['district'], as_index = False)[['middle_avg_bill']].median().sort_values(by='middle_avg_bill', ascending=False)
display(a_1)
fig = px.bar(a_1,
x='district',
y='middle_avg_bill',
title='медианное среднего чека по округам Москвы')
fig.update_xaxes(tickangle=45)
fig.show()
| district | middle_avg_bill | |
|---|---|---|
| 1 | ЗАО | 1000.0 |
| 5 | ЦАО | 1000.0 |
| 4 | СЗАО | 700.0 |
| 2 | САО | 650.0 |
| 8 | ЮЗАО | 600.0 |
| 0 | ВАО | 575.0 |
| 3 | СВАО | 500.0 |
| 6 | ЮАО | 500.0 |
| 7 | ЮВАО | 450.0 |
bill_df = data1.groupby('district', as_index=False)['middle_avg_bill'].agg('median')
bill_df
# загружаем JSON-файл с границами округов Москвы
state_geo = '/datasets/admin_level_geomap.geojson'
# moscow_lat - широта центра Москвы, moscow_lng - долгота центра Москвы
moscow_lat, moscow_lng = 55.751244, 37.618423
# создаём карту Москвы
m = Map(location=[moscow_lat, moscow_lng], zoom_start=10)
# создаём хороплет с помощью конструктора Choropleth и добавляем его на карту
Choropleth(
geo_data=state_geo,
data=bill_df,
columns=['district', 'middle_avg_bill'],
key_on='feature.name',
fill_color='YlGn',
fill_opacity=0.8,
legend_name='Медианный средний чек по районам',
).add_to(m)
# выводим карту
m
Четверка лидеров по медианному среднему чеку по всем категориям ЦАО и ЗАО - 1000р, и СЗАО(700р) и САО(650р). Удаленность от центра уменьшаем средний чек заведений, но только не в ЗАО.
В датасете представлено 8406 заведений обществвенного питания Москвы.
Датасет хранит 7 строковых столбцов, 6 численных и 1 целочисленный.
Явные дубликаты отсутствуют даже после приведения всех названий к строчному виду
Пропуски:
Максимальное количество пропусков в столбце 'middle_coffee_cup' - 93,6% пропусков в столбце. Дозаполнить или удалить тут пропуски невозможно, оставили так как есть.
Еще один столбец с пропусками 62,5% - это 'middle_avg_bill' (срединный средний чек) - это числовое значение столбца 'avg_bill'(средний чек). Исследовав самую распространенную сеть в Москве "Шоколадница" - мы определили что даже в ней нет однородности цен, посмотрели уровень цен заведений по категориям и округам - тоже нет однородности поэтому столбец 'middle_avg_bill' оставили так как есть.
Еще один недозаполненный столбец 'price' - это градация заведений по среднему чеку - 60% пропуски. Уровень цен в категориях варьируется и зависит от слишком многих факторов, поэтому оставили данные в столбце так как есть.
Кроме того есть пропуски в столбце 'hours' - часы работы 6,4% это не существенно, пропуски заполнили "unknown" для дальнейшей работы.
Столбец "seats" - количество посадочных мест - пропуски это может быть как незаполненность данных, так и заведения с полным отсутствием посадочных мест, поэтому этот столбец тоже оставим "как есть".
Самые популярные часы работы заведений общественного питания с 10.00 до 22.00 и ежедневно, круглосуточно.
Распределение по уровню цен в Москве:
2/3 заведений имеют средние цены.
почти треть заведений имеют уровень цен высокие и выше среднего
и лишь 5% - каждое двадцатое заведение имеет низкие цены.
В Москве нет места низким ценам, очевидно из-за высокой стоимости аренды
Медианные и средние цены не совпадают даже в этом небольшом количестве полностью заполненных данных. Медианная цена во всех ценовых категориях ниже чем средняя, что свидетельствует о выбросах вверх.
Следовательно ставка большей части заведений в Москве - невысокий средний чек и большое число посетителей.
Мы видим, что в первичных данных
Причем на заведения длительного пребывавания (кафе и рестораны) приходится чуть больше половины 52,6%. Остальную часть рынка занимают заведения быстрого питания, еда на вынос и заведения с барными стойками. Если учесть что мы рассматриваем данные лета 2022 года, а до этого были самоизоляция и ограничения в работе заведений общественного питания. Разрешена была работа "на вынос" и доставка, и продолжалось это с марта 2020 года до марта 2022 года, то разумно предположить что из-за ограничений в работе заведения общественного питания длительного пребывания просто разорились и не выдержали конкуренции с предприятиями небольшого формата и большой мобильности. Есть предположение что из маленького формата пиццерий, кофеен и баров/пабов вырастают вполне себе большие рестораны и кафе
Что удивительно в ЗАО сосредоточены заведения-гиганты общественного питания (1288 посадочных мест).
Тройка категорий-лидеров по посадочным местам: ресторан (86), бар/паб (82) и кофейня (80).
Похоже кофейный бизнес идет хорошо, количество посадочных мест догоняет рестораны и пабы и, что удивительно - кофейни перегнали столовые! Еще одна аномалия - бары/пабы по среднему количеству посадочных мест обгоняют рестораны.
Судя по всему посадочные места на барных стульях весьма популярны в Москве, то есть компактное размещение посетителей в Барах/пабах и кофейнях выгдно сказывается на бизнесе.
В целом медианное количество посадочных мест в заведениях Москвы от 50 до 86 и в целом не превышает 100.
В в сетевых:
кафе, ресторанах, кофейнях, быстром питании и столовых медиана посадочных мест больше.
В булочных медиана одинаковая вне принадлежности к сетям.
В не сетевых:
барах/пабах и пиццериях медиана посадочных мест больше.
Максимальная разница в медиане посадочных мест между сетевым и несетевым заведением в категории кофейня.
Сетевая кофейня даст приблизительно +30 посадочных мест к не сетевой.
Большинство заведений на рынке среднего формата по посадочным местам.
Сетевых заведений почти в 2 раза меньше чем не сетевых (или 1/3 и 2/3) в общем датасете. Открыть франшизу и пользоваться всеми привилегиями сети дороже чем быть пионером и открывать собственное заведение.
Если в не сетевых заведениях лидируют кафе, рестораны и кофейни(кафе существенно больше), то в сетевых - доли кафе, ресторанов и кофеен практически одинаковые. Значит открыть сетевое заведение - ресторан, кафе или кофейню выбирают равное количество инвесторов, вопрос видимо в уровне вложений - у кого сколько есть.
Булочных, пиццерий и кофеен сетевых больше чем не сетевых. И наоборот кафе, ресторанов и пабов/баров больше не сетевых.
В ТОП15 по количеству заведений - больше всего 7 - категория кофейня, 3 кафе, 2 пиццерии, 2 ресторана и 1 булочная. Следовательно, 7 кофеен из 15 - это 47%. Значит, кофейни лидируют в количественном топе заведений общественного питания Москвы. Скорее всего очевиден факт уменьшения доли крупных заведений в ресторанном бизнесе и заполнение образовавшегося места на рынке сетями кофеен и булочных, которые в период самоизоляции освоили онлайн формат и еду на вынос и быстро перестроились.
В ТОП15 попали известные сети, небольшого формата и среднего уровня цен и большой проходимости, часто их можно встретить возле станций метро в Москве.
В ЮАО, СВАО и ЮВАО - дефицит сетевых заведений общественного питания, а в ЗАО и ЮЗАО их слишком много.
А не сетевые заведения сосредоточены в ВАО и ЮВАО, а в ЦАО и ЗАО их, вполне вероятно, не хватает.
Во всех округах города Москвы кроме Центрального лидируют кафе. В топ3 по округам входят еще рестораны и кофейни. На последнем месте по распространенности - булочные и столовые.
Визуально больше всего заведений общественного питания в Центральном округе, меньше всего - в Северо-Западном.
Если предположить что количество кофеен может "дорасти" до количества кафе как в ЦАО, то Хуже всего потенциал для роста числа кофеен в ЦАО, СЗАО(там мало заведений вообще) и в САО. Лучше всего потенциал ЮВАО и ВАО, на втором месте по возможности роста ЮЗАО и ЮАО. В остальных округах просто есть потенциал для открытия кофеен.
Низкие цены существенно портят рейтинги в барах/пабах и быстром питании. Цены выше среднего портят рейтинги столовых. Средние цены снижают рейтинг булочных.
Самый высокий средний рейтинг 4,5 в ресторанах с высокими ценами - оно и понятно, скорее всего часть выручки рестораны тратят на оборудование, сотрудников и интерьер, а это благотворно сказывается на рейтинге.
Самый низкий средний рейтинг в барах/пабах с низкими ценами 3,9.
Самый однородный средний рейтинг от 4,3 до 4,4 у кофеен, то есть какие бы цены не были у кофеен, ее рейтинг будет достаточно высоким.
Центральный район всегда на пункт выше имеет рейтинг почти во всех категориях.
С рейтингом кофеен хуже всего обстоит дело в ЗАО - 4,2, в остальных районах 4,3
На карте видно что лучшие рейтинги в ЦАО, худшие в СВАО и ЮВАО.
Меньше всего заведений на юге и юго-востоке. Больше всего в центре. Также запад Москвы плохо заполнен заведениями общественного питания.
Особенности ЦАО - он лидирует по всем параметрам:
самые высокие чеки
самые высокие рейтинги
больше всего заведений во всех категориях.
Там больше всего денег, но также там
САМАЯ БОЛЬШАЯ аренда и
много силовых ведомств, которые не способствуют ведению бизнеса.
Отличительной особенностью ЦАО является большая концентрация достопримечательностей и туристических мест. Кроме того много административных учреждений и бизнес-центров тоже находится там. Театры, выставочные залы, храмы, университеты - все это привлекает много посетителей в заведения общественного питания.
Улицы с максимальным количеством заведений:
В ТОП15 улиц с самым большим количеством заведений попали не только улицы загруженные общепитом, но и просто очень длинные улицы. Когда мы добавили протяженность улиц и рассчитали плотность загрузки заведениями - результат ТОП 15 изменился и лидером стала Пятницкая улица на которой плотность самая высокая - 26 заведений на 1 км. А МКАД с 10-го места переместился на последнее, просто потому что его протяженость 108 км.
На самых загруженных улицах:
Улицы с минимальным(1) количеством заведений:
Такие заведения общепита раскинуты практически по всей территории города и как правило располагаются вдали от основных дорог, в промзонах, рядом с парками.
У единственных заведений на улице:
Больше всего улиц с одним заведением в ЦАО(там много коротких улочек, на которых по одному заведению) почти в три раза больше чем в СВАО, ВАО и САО. В остальных округах еще меньше.
Самый плохой рейтинг в ЮВАО, 3.0.
В одиноких заведениях есть 2 пика уровня среднего чека - на 200р и на 1600р. Скорее всего - это 2 уровня обслуживания - быстрое питание (кофе и булочка или шаурма) и второй вариант 1600р - это заведение длительного пребывания с средним чеком полноценного обеда.
Визуально - больше всего таких улиц с одним заведений в центре и это связано с их короткой протяженностью.
Кофейни входят в топ3 как сетевых, так и не сетевых заведений общественного питания.
Таким образом, сети заведений общественного питания и частники открывают в центре на маленьких улочках одинокие заведения потому, что плотность потока людей достаточна в ЦАО не только для окупаемости высокой аренды, но и для прибыли.
Больше всего денег в ЦАО, далее следует ЗАО и ВАО. Меньше всего денег в ЮВАО.
Четверка лидеров по медианному среднему чеку по всем категориям ЦАО и ЗАО - 1000р, и СЗАО(700р) и САО(650р).
Удаленность от центра уменьшаем средний чек заведений, но только не в ЗАО.
проиллюстрируйте другие взаимосвязи, которые вы нашли в данных. Например, по желанию исследуйте часы работы заведений и их зависимость от расположения и категории заведения. Также можно исследовать особенности заведений с плохими рейтингами, средние чеки в таких местах и распределение по категориям заведений.
# график среднего чека в категории сетевое кафе, цены низкие
plt.figure(figsize=(20, 15))
data0 = data.query('middle_avg_bill < 6000 and chain==1 and category=="кофейня" and price=="средние"')
sns.distplot(data0['middle_avg_bill'])
plt.show()
В сетевая кофейня со средними ценами средний чек имеет 2 пика на 320 и 800 рублей - это видимо 2 уровня обслуживания (быстрое питание и кафе/ресторан).
# построим общий pairplot
sns.pairplot(data)
plt.show()
**Рейтинг и количество мест в заведениях обратно пропорционально.
Средний чек выше в центре.
Средний чек выше в небольших заведениях.
Есть средняя цена за чашку каппучино в заведениях до 600 посадочных мест.
# построим диаграмму рассеяния среднего чека по категориям цен и принадлежности к сети
plt.figure(figsize=(15, 7))
sns.stripplot(x='price', y='middle_avg_bill', hue='chain', data=data1)
plt.title('точечная диаграмма распределения среднего чека в зависимости от категории цен')
plt.xlabel('категория цен')
plt.ylabel('средний чек')
plt.show()
Самый большой разброс среднего чека в категории цен - высокие, самый маленький - низкие. В низких ценах нет люфта для разнообразия цен, а в категории средние цены максимум заведений и, как следствие, большая конкуренция делает цены однородными. В категории высокие цены - у сетевых заведений цены ниже, у не сетевых максимум среднего чека 35000 рублей.
# построим диаграмму среднего чека по категориям цен и принадлежности к сетям
a = data.groupby(['price', 'chain'], as_index = False)[['middle_avg_bill']].median()
# используем стиль dark из библиотеки seaborn
sns.set_style('dark')
# назначаем размер графика
plt.figure(figsize=(10, 4))
# строим столбчатый график средствами seaborn
sns.barplot(x='middle_avg_bill', y='price', data=data1, hue='chain')
# формируем заголовок графика и подписи осей средствами matplotlib
plt.title('График зависимости среднего чека от категории заведения')
plt.xlabel('средний чек')
plt.ylabel('категория цен заведения')
# поворачиваем подписи значений по оси X на 45 градусов
plt.xticks(rotation=45)
# выбираем положение легенды и указываем размер шрифта
plt.legend(loc='lower right', fontsize=10)
# добавляем сетку
plt.grid()
# отображаем график на экране
plt.show()
разница высоких цен между сетевым и не сетевым заведением больше всего в категории высоких цен
# violinplot - Распределение среднего чека в зависимости от принадлежности к сети
plt.figure(figsize=(15, 7))
# применяем стиль darkgrid из библиотеки seaborn
sns.set_style('darkgrid')
# строим график violinplot средствами seaborn
sns.violinplot(x='chain', y='middle_avg_bill', data=data)
# ограничиваем ось X для наглядности
plt.xlim(-1, 3)
plt.ylim(-1000, 4500)
# указываем заголовок графика и подписи осей средствами matplotlib
plt.title('Распределение среднего чека в зависимости от принадлежности к сети')
plt.ylabel('Значение среднего чека')
# поворачиваем подписи значений по оси X на 45 градусов
plt.xticks(rotation=45)
plt.xlabel(None)
# отображаем график на экране
plt.show()
Распределение среднего чека в зависимости от принадлежности к сети - в сетях есть 2 пика, в несетевых только один, значит несетевые заведения гибче адаптируются к рынку и не имеют жестких цен.
# диаграмма рассеяния рейтинга в зависимости от среднего чека
plt.figure(figsize=(15, 7))
# вручную задаём параметры графика
sns.set_style('darkgrid',
{'axes.facecolor': '0.8',
'grid.color': '0.1',
'figure.facecolor': '0.95'})
# строим график scatter средствами seaborn
sns.scatterplot(x='middle_avg_bill', y='rating', hue='chain', data=data)
plt.xlim(0, 8000)
# отображаем график на экране
plt.show()
Высокие рейтинги не зависят от среднего чека чека заведения. Низкие рейтинги сосредоточены на уровне среднего чека до 1000 рублей.
# распределение суммарного среднего чека по уровню цен
fig = go.Figure(data=[go.Pie(labels=data['price'].dropna(), values=data['middle_avg_bill'])])
fig.show()
максимальные деньги зарабатывают в категории средних цен - практически 2/3 от всех средних чеков.
# # распределение суммарного среднего чека по категориям
fig = go.Figure(data=[go.Pie(labels=data['category'].dropna(), values=data['middle_avg_bill'])])
fig.show()
Максимум денег зарабатывают рестораны, меньше всего булочные.
Кофейня может развиваться так: + пицца, далее + бар, Далее + трансформация в кафе, далее + статус ресторана.
Именно так идет распределение денежных поступлений
# распределение суммарного среднего чека по округам
fig = go.Figure(data=[go.Pie(labels=data['district'].dropna(), values=data['middle_avg_bill'])])
fig.show()
ЗАО на втором месте по количеству денег, но в 4 раза дешевле ЦАО
data_avg_bill = data['avg_bill'].value_counts().reset_index()
data_avg_bill.head()
display('количество средних чеков в датасете', data_avg_bill['avg_bill'].count())
data_avg_bill.columns = ['avg_bill', 'count']
data_avg_bill_total = data_avg_bill['count'].sum()
data_avg_bill_total
# насколько часто встречается эта цена в датасете
data_avg_bill['share'] = round(100*data_avg_bill['count']/data_avg_bill_total, 2)
data_avg_bill.info()
data_avg_bill.head(10)
'количество средних чеков в датасете'
897
<class 'pandas.core.frame.DataFrame'> RangeIndex: 897 entries, 0 to 896 Data columns (total 3 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 avg_bill 897 non-null object 1 count 897 non-null int64 2 share 897 non-null float64 dtypes: float64(1), int64(1), object(1) memory usage: 21.1+ KB
| avg_bill | count | share | |
|---|---|---|---|
| 0 | Средний счёт:1000–1500 ₽ | 241 | 6.32 |
| 1 | Средний счёт:1500–2000 ₽ | 120 | 3.14 |
| 2 | Средний счёт:300–500 ₽ | 90 | 2.36 |
| 3 | Средний счёт:500–1000 ₽ | 78 | 2.04 |
| 4 | Средний счёт:1500–2500 ₽ | 68 | 1.78 |
| 5 | Средний счёт:700–1000 ₽ | 50 | 1.31 |
| 6 | Средний счёт:от 1500 ₽ | 48 | 1.26 |
| 7 | Средний счёт:1000 ₽ | 43 | 1.13 |
| 8 | Средний счёт:1500 ₽ | 43 | 1.13 |
| 9 | Цена чашки капучино:239–274 ₽ | 43 | 1.13 |
в 5-ти из 10 позиций цены выше 1000 рублей. Диапазан топ значений средних чеков от 300 до 2000 рублей. В сумме количество топ 10 значений чеков составляют почти половину от общего числа. Средний чек 1000-1500р встречается в 2 раза чаще(6,32%) чем второй по распространенности 1500-2000р (3,14%).
data_cup = data['middle_coffee_cup'].value_counts()
display('количество средних чеков в датасете', data['middle_coffee_cup'].count())
#
data_cup = data_cup.reset_index()
data_cup.columns = ['middle_coffee_cup', 'count']
data_cup_total = data_cup['count'].sum()
data_cup['share'] = round(100*data_cup['count']/data_cup_total, 2) # насколько часто встречается эта цена в датасете
#data_cup.info()
data_cup.head(10)
'количество средних чеков в датасете'
535
| middle_coffee_cup | count | share | |
|---|---|---|---|
| 0 | 256.0 | 43 | 8.04 |
| 1 | 60.0 | 33 | 6.17 |
| 2 | 95.0 | 32 | 5.98 |
| 3 | 150.0 | 24 | 4.49 |
| 4 | 170.0 | 18 | 3.36 |
| 5 | 200.0 | 18 | 3.36 |
| 6 | 225.0 | 15 | 2.80 |
| 7 | 250.0 | 15 | 2.80 |
| 8 | 135.0 | 14 | 2.62 |
| 9 | 120.0 | 13 | 2.43 |
В датасете лидирует цена чашки кофе 256 рублей встречается в 8,25% случаев, на втором месте - 60 рублей встречается в 6,33% случаев, на третьем - 95 рублей встречается в 6,14% случаев.
Основателям фонда «Shut Up and Take My Money» не даёт покоя успех сериала «Друзья». Их мечта — открыть такую же крутую и доступную, как «Central Perk», кофейню в Москве. Будем считать, что заказчики не боятся конкуренции в этой сфере, ведь кофеен в больших городах уже достаточно. Попробуем определить, осуществима ли мечта клиентов.
Ответим на следующие вопросы:
data_=data1.query('category == "кофейня"')
display(data_.head())
print('Всего кофеен в датасете: ')
data_['name'].count()
| name | category | address | district | hours | lat | lng | rating | price | avg_bill | middle_avg_bill | middle_coffee_cup | chain | seats | street | is_24/7 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 3 | dormouse coffee shop | кофейня | москва, улица маршала федоренко, 12 | Северный административный округ | ежедневно, 09:00–22:00 | 55.881608 | 37.488860 | 5.0 | NaN | Цена чашки капучино:155–185 ₽ | NaN | 170.0 | 0 | NaN | улица маршала федоренко | False |
| 25 | в парке вкуснее | кофейня | москва, парк левобережный | Северный административный округ | ежедневно, 10:00–21:00 | 55.878453 | 37.460028 | 4.3 | NaN | NaN | NaN | NaN | 1 | NaN | парк левобережный | False |
| 45 | 9 bar coffee | кофейня | москва, коровинское шоссе, 41, стр. 1 | Северный административный округ | пн-пт 08:00–18:00 | 55.885837 | 37.513422 | 4.0 | NaN | NaN | NaN | NaN | 1 | 46.0 | коровинское шоссе | False |
| 46 | cofefest | кофейня | москва, улица маршала федоренко, 6с1 | Северный административный округ | пн-пт 09:00–19:00 | 55.879934 | 37.492522 | 3.6 | NaN | NaN | NaN | NaN | 1 | NaN | улица маршала федоренко | False |
| 52 | cofix | кофейня | москва, улица дыбенко, 7/1 | Северный административный округ | ежедневно, 08:00–22:00 | 55.878531 | 37.479395 | 3.8 | NaN | NaN | NaN | NaN | 1 | NaN | улица дыбенко | False |
Всего кофеен в датасете:
1413
# посмотрим распределение кофеен сетевые(1) / не сетевые(0)
data_['chain'].value_counts()
1 720 0 693 Name: chain, dtype: int64
Сетевых кофеен на 2% больше чем не сетевых.
caffe_data_count = data_.groupby('district')['name'].count().sort_values(ascending=False)
caffe_data_count.reset_index()
caffe_data_count_total = caffe_data_count.sum()
caffe_data_count_share = round(100 * caffe_data_count/caffe_data_count_total, 0)
print('Доли кофеен по округам Москвы, %')
caffe_data_count_share
Доли кофеен по округам Москвы, %
district Центральный административный округ 30.0 Северный административный округ 14.0 Северо-Восточный административный округ 11.0 Западный административный округ 11.0 Южный административный округ 9.0 Восточный административный округ 7.0 Юго-Западный административный округ 7.0 Юго-Восточный административный округ 6.0 Северо-Западный административный округ 4.0 Name: name, dtype: float64
# moscow_lat - широта центра Москвы, moscow_lng - долгота центра Москвы
moscow_lat, moscow_lng = 55.751244, 37.618423
# создаём карту Москвы
m = Map(location=[moscow_lat, moscow_lng], zoom_start=10)
# создаём пустой кластер, добавляем его на карту
marker_cluster = MarkerCluster().add_to(m)
# пишем функцию, которая принимает строку датафрейма,
# создаёт маркер в текущей точке и добавляет его в кластер marker_cluster
def create_clusters(row):
Marker(
[row['lat'], row['lng']],
popup=f"{row['name']} {row['rating']}",
).add_to(marker_cluster)
# применяем функцию create_clusters() к каждой строке датафрейма
data_.apply(create_clusters, axis=1)
# выводим карту
m
В ЦАО больше всего кофеен 30%, САО на втором месте - 14%, на третьем месте СВАО и ЗАО - 11%. в СЗАО кофеен почти в 8 раз меньше чем в ЦАО. Вывод - чем ближе к центру, тем больше кофеен. Юг, Юго-восток, Северо-запад и Восток Москвы - мало кофеен, средняя оснащенность кофейнями - запад и северо-восток.
data_24_7=data_[data_['is_24/7']==True]
data_24_7.head()
| name | category | address | district | hours | lat | lng | rating | price | avg_bill | middle_avg_bill | middle_coffee_cup | chain | seats | street | is_24/7 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 200 | wild bean | кофейня | москва, дмитровское шоссе, 107е | Северный административный округ | ежедневно, круглосуточно | 55.878477 | 37.543426 | 3.5 | NaN | NaN | NaN | NaN | 1 | 20.0 | дмитровское шоссе | True |
| 971 | wild bean cafe | кофейня | москва, ярославское шоссе, 116 | Северо-Восточный административный округ | ежедневно, круглосуточно | 55.867822 | 37.708853 | 4.0 | NaN | NaN | NaN | NaN | 1 | 25.0 | ярославское шоссе | True |
| 1047 | wild bean cafe | кофейня | москва, ярославское шоссе, вл3с3 | Северо-Восточный административный округ | ежедневно, круглосуточно | 55.851778 | 37.676423 | 4.5 | NaN | Цена чашки капучино:140–200 ₽ | NaN | 170.0 | 1 | NaN | ярославское шоссе | True |
| 1214 | wild bean cafe | кофейня | москва, мкад, 65-й километр, 8 | Северо-Западный административный округ | ежедневно, круглосуточно | 55.813787 | 37.390701 | 4.3 | NaN | NaN | NaN | NaN | 1 | NaN | мкад | True |
| 1291 | шоколадница | кофейня | москва, улица народного ополчения, 49, корп. 1 | Северо-Западный административный округ | ежедневно, круглосуточно | 55.794815 | 37.494834 | 4.2 | средние | Средний счёт:650–850 ₽ | 750.0 | NaN | 1 | 200.0 | улица народного ополчения | True |
bill_coffe_df = data_.groupby('district', as_index=False)['is_24/7'].agg('sum')
bill_coffe_df
# загружаем JSON-файл с границами округов Москвы
state_geo = '/datasets/admin_level_geomap.geojson'
# moscow_lat - широта центра Москвы, moscow_lng - долгота центра Москвы
moscow_lat, moscow_lng = 55.751244, 37.618423
# создаём карту Москвы
m = Map(location=[moscow_lat, moscow_lng], zoom_start=10)
# создаём хороплет с помощью конструктора Choropleth и добавляем его на карту
Choropleth(
geo_data=state_geo,
data=bill_coffe_df,
columns=['district', 'is_24/7'],
key_on='feature.name',
fill_color='YlGn',
fill_opacity=0.8,
legend_name='Распределение круглосуточных заведений по районам',
).add_to(m)
# выводим карту
m
Круглосуточных немного, однако на таком графике затруднительно считывать их непосредственное количество Построим карту с наесенными заведениями - кофейни 24/7.
# moscow_lat - широта центра Москвы, moscow_lng - долгота центра Москвы
moscow_lat, moscow_lng = 55.751244, 37.618423
# создаём карту Москвы
m = Map(location=[moscow_lat, moscow_lng], zoom_start=10)
# создаём пустой кластер, добавляем его на карту
marker_cluster = MarkerCluster().add_to(m)
# пишем функцию, которая принимает строку датафрейма,
# создаёт маркер в текущей точке и добавляет его в кластер marker_cluster
def create_clusters(row):
Marker(
[row['lat'], row['lng']],
popup=f"{row['name']} {row['rating']}",
).add_to(marker_cluster)
data_24_7_coffe= data_24_7.query('category == "кофейня"')
# применяем функцию create_clusters() к каждой строке датафрейма
data_24_7_coffe.apply(create_clusters, axis=1)
# выводим карту
m
Круглосуточные кофейни есть и они сосредоточены в основном в ЦАО. В ЗАО и ЮЗАО их меньше от 5 до 9. В остальных районах их от 1 до 5. Сосредоточены кофейни в районе вокзалов и транспортных артерий.
bill_coffe_df = data_.groupby('district', as_index=False)['rating'].agg('median')
bill_coffe_df
# загружаем JSON-файл с границами округов Москвы
state_geo = '/datasets/admin_level_geomap.geojson'
# moscow_lat - широта центра Москвы, moscow_lng - долгота центра Москвы
moscow_lat, moscow_lng = 55.751244, 37.618423
# создаём карту Москвы
m = Map(location=[moscow_lat, moscow_lng], zoom_start=10)
# создаём хороплет с помощью конструктора Choropleth и добавляем его на карту
Choropleth(
geo_data=state_geo,
data=bill_coffe_df,
columns=['district', 'rating'],
key_on='feature.name',
fill_color='YlGn',
fill_opacity=0.8,
legend_name='Медианный рейтинг заведений по районам',
).add_to(m)
# выводим карту
m
Рейтинги у кофеен от 4,2 до 4,4. Самые низкие рейтинги у кофеен ЗАО от 4,2 до 4,22. В остальных райтона около 4,3.
plt.figure(figsize=(10, 15))
sns.violinplot(x='middle_coffee_cup', y='district', hue='chain', data=data, palette='rainbow')
# ограничиваем ось X для наглядности
plt.xlim(0, 500)
# указываем заголовок графика и подписи осей средствами matplotlib
plt.title('Распределение среднего чека каппучино в зависимости от округа Москвы')
plt.xlabel('средний чек за чашку каппучино')
plt.ylabel('Округа Москвы')
# отображаем график на экране
plt.show()
Средний чек в не сетевых заведениях за чашку каппучино от 130 до 200 рублей в зависимости от районов, в сетевых - от 80 до 260. Максимальный средний чек в сетевых в ЗАО и ЦАО. Не сетевых ЗАО.
Всего кофеен в датасете: 1413, это 16,8%
Доли кофеен по округам Москвы, %%
Центральный административный округ 30.0
Северный административный округ 14.0
Северо-Восточный административный округ 11.0
Западный административный округ 11.0
Южный административный округ 9.0
Восточный административный округ 7.0
Юго-Западный административный округ 7.0
Юго-Восточный административный округ 6.0
Северо-Западный административный округ 4.0
В ЦАО больше всего кофеен 30%, САО на втором месте - 14%, на третьем месте СВАО и ЗАО - 11%. в СЗАО кофеен почти в 8 раз меньше чем в ЦАО. Вывод - чем ближе к центру, тем больше кофеен. Юг, Юго-восток, Юго-запад, Северо-запад и Восток Москвы - мало кофеен, средняя оснащенность кофейнями - север, запад и северо-восток.
Круглосуточные кофейни есть и они сосредоточены в основном в ЦАО(22-26). В ЗАО и ЮЗАО их меньше от 5 до 9. В остальных районах их от 1 до 5. Сосредоточены кофейни в районе вокзалов и транспортных артерий.
Рейтинги у кофеен от 4,2 до 4,3. Самые низкие рейтинги у кофеен ЗАО от 4,2 до 4,22. В остальных райтона около 4,3.
Средний чек с несетевых заведениях за чашку каппучино от 130 до 200 рублей в зависимости от районов, в сетевых - от 80 до 260. Максимальный средний чек в сетевых - в ЗАО и ЦАО. Не сетевых - в ЗАО.
Исследуя данные мы обнаружили что максимальное количество пропусков в столбце 'middle_coffee_cup', поэтому рекомендации по ценам для кофеен должны быть исследованы перед открытием эмпирически (сами ходим и смотрим прайсы конкурентов). Ценовой диапазон среднего чека "пляшет" даже в рамках одной сети кофеен.
Распределение по уровню цен в Москве 2/3 заведений имеют средние цены. Пятая часть заведений имеют уровень цен высокие и выше среднего и лишь 5% - каждое двадцатое заведение имеет низкие цены. Следовательно выбирать лучше средний уровень цен.
Самые популярные часы работы заведений общественного питания с 10.00 до 22.00 и ежедневно, круглосуточно. Лучше выбрать круглосуточно.
В первичных данных на третьем месте по количеству - кофейни 16,8% доли от рынка. Кофейни входят в топ 3 как сетевых так и не сетевых заведений. Данные сосавлены на лето 2022 года, то есть через 3 месяца после отмены ограничений в заведениях общественного питания во время короновируса. Заведениям длительного пребывания принадлежит 52% рынка, остальное - бары, пиццерии, быстрое питание и прочее. Выбирая формат - можно ориентироваться как на длительное пребывание, так и на кратковременное. Посадочные места с барными стойками весьма популярны в Москве - это экономия места, увеличение количества посадочных мест и повышение пропускной способности заведения.
По медианному значению посадочных мест третьем месте кофейни - 80 посадочных мест. Похоже кофейный бизнес идет хорошо количество посадочных мест догоняет рестораны и пабы и, что удивительно - кофейни перегнали столовые!.
В целом медианное количество посадочных мест в заведениях Москвы от 50 до 86 (не более 100).
В сетевых кофейнях медианное число посадочных мест больше на 30 чем в не сетевых. Сетевых заведений почти в 2 раза меньше чем не сетевых. Это и понятно открыть франшизу и пользоваться всеми привилегиями сети стоит дороже, чем быть пионером и открывать собственное заведение в соответствии с законами РФ.
Если в несетевых заведениях явно лидируют кафе. В топ3 также входят рестораны и кофейни, то в сетевых - доли кафе, ресторанов и кофеен практически одинаковые. Значит, открыть сетевое заведение - ресторан, кафе или кофейню выбирают равное количество инвесторов, вопрос видимо в уровне вложений - у кого сколько есть. Кофеен сетевых больше чем не сетевых. И наоборот кафе, ресторанов и пабов/баров больше не сетевых. Очевидный вывод: кофейни чаще открывают сетевые.
В топе по количеству сетей заведений - больше всего кофеен 7 из 15 это 40% из топа, следвательно кофейни лидируют в топе Москвы. Также видно что из маленьких заведений кофейни и булочные "дорастают" до настоящих пабов/баров, кафе и ресторанов. Скорее всего очевиден факт уменьшения доли крупных заведений в ресторанном бизнесе и заполнение образовавшегося места на рынке сетями кофеен и булочных, которые в период самоизоляции освоили онлайн формат и еду на вынос и быстро перестроились. Да это всё известные сети, часто их можно встретить возле станций метро в Москве. Prime раньше была сетью кофеен из Ижевска еще год назад она была самой крупной сетью с 850 заведениями по всей России, а теперть они позиционируют себя как кафе. Лавка Братьев Караваевых явно была булочной, а теперь ресторан. Яндекс лавка была доставкой продуктов, а теперь пиццерия. Самая распространенная сеть в Москве - это Шоколадница - скорее всего эта старая сеть ресторанов консолидировано решала проблемные вопросы в кризис и у нас в датасете теперь это кофейня, возможно их маркетологи пришли к выводу что сокращение формата - это путь к выживанию. Признак который объединяет эти сети - почти все они имеют заведения малого формата.
Можно предположить что в ЮАО, СВАО и ЮВАО - дефицит сетевых заведений общественного питания, а в ЗАО их слишком много.
А не сетевые заведения сосредоточены в ЮАО, СВАО, ВАО и ЮВАО, а в ЗАО их вполне вероятно не хватает.
Во всех округах города Москвы кроме Центрального лидируют кафе, кофейни - на втором месте в САО. Во всех округах кроме Северного на третьем месте кофейни.
Хуже всего потенциал для роста числа кофеен в ЦАО, СЗАО(там мало заведений вообще) и в САО.
Самый однородный средний рейтинг от 4,3 до 4,4 у кофеен.
С рейтингом кофеен хуже всего обстоит дело в ЗАО - 4,2, в остальных райнах 4,3
Безусловным лидером является проспект Мира, на нем больше всего кафе, ресторанов и кофеен.
На одной из топ улиц улице кофеен и ресторанов поровну.
Не подходят для открытия кофеен (слабый потенциал развития) Кутузовский проспект, Варшавское шоссе, Профсоюзная, Вавилова, и Люблинская улицы - здесь их открыто примерно столько же сколько кафе и ресторанов. Также не подходит МКАД из-за скоростного режима и запрета остановок.**
Хороший потенциал открытия кофейни в ВАО, СВАО, ЮВАО, ЮЗАО, ЮАО. Чуть похуже - ЗАО. Центральный и СЗАО не рассматриваем - в первом максимум кокофеен, во втором вообще мало заведений (скорее всего из-за отсутствия посетителей).
Больше всего денег в ЦАО, далее следует ЗАО и ВАО.
Тройка лидеров по медианному среднему чеку по всем категориям ЦАО и ЗАО - 1000р, и СЗАО 700р. Удаленность от центра уменьшаем средний чек заведений, но только не в ЗАО.
Исходя из всего вышеперечисленного: Я бы рекомендовала открыть камерную кофейню в ЗАО или сетевую, но с высоким средним рейтингом. Можно выбрать проспект Вернадского - на нем расположено 3 станции метро - как одну из больших транспортных артерий 15% заведений выбирают такой вариант. Или второй вариант улица Студенческая - крорткая улица в районе Киевского вокзала(такой выбор делает каждое 20-е заведение), есть многочисленная студенческая популяция рядом и нет кофеен. Если инвестор опытный в ресторанном бизнесе - то лучше открыть не сетевое заведение в районе между Киевским вокзалом и метро Парк Победы. Вокзал сосредоточение покупателей, метро Парк Победы - место проведения досуга москвичей. В этом округе максимум денег, плохие рейтинги у кофеен и их мало. Рекомендовано открыть кофейню 24/7 - из-за близости вокзала и с количеством посадочных мест около 80. Если опыта в ресторанном бизнесе нет - то лучше открыть сетевое заведение со средними ценами (160-190 рублей за чашку каппучино) и максимальным рейтингом(от 4,3). В любом случае настоящий момент работает на инвестора - разорение части больших ресторанов дает возможности и потенциал роста и масштабирования бизнеса. Людям надоело есть холодную еду на вынос и использовать пластиковые тарелки.
Презентация https://disk.yandex.ru/i/S95Bp0OUNxwFYQ